@lingyao037/openclaw-lingyao-cli 0.3.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3332 @@
1
+ // src/runtime.ts
2
+ var globalRuntime = null;
3
+ function setRuntime(runtime) {
4
+ globalRuntime = runtime;
5
+ }
6
+
7
+ // src/adapters/config.ts
8
+ function extractAccounts(cfg) {
9
+ const channels = cfg?.channels;
10
+ const lingyao = channels?.lingyao;
11
+ const accounts = lingyao?.accounts;
12
+ if (!accounts) return {};
13
+ return accounts;
14
+ }
15
+ function createConfigAdapter() {
16
+ return {
17
+ listAccountIds(cfg) {
18
+ const accounts = extractAccounts(cfg);
19
+ return Object.keys(accounts);
20
+ },
21
+ resolveAccount(cfg, accountId) {
22
+ const accounts = extractAccounts(cfg);
23
+ const ids = Object.keys(accounts);
24
+ if (ids.length === 0) {
25
+ accounts["default"] = {};
26
+ }
27
+ const resolvedId = accountId ?? (ids.includes("default") ? "default" : ids[0]);
28
+ if (!resolvedId) {
29
+ throw new Error("No lingyao accounts configured");
30
+ }
31
+ const accountConfig = accounts[resolvedId];
32
+ if (!accountConfig) {
33
+ throw new Error(`Account "${resolvedId}" not found`);
34
+ }
35
+ return {
36
+ id: resolvedId,
37
+ enabled: accountConfig?.enabled !== false,
38
+ dmPolicy: accountConfig?.dmPolicy ?? "paired",
39
+ allowFrom: Object.freeze(accountConfig?.allowFrom ?? []),
40
+ rawConfig: accountConfig
41
+ };
42
+ },
43
+ isConfigured(_account) {
44
+ return true;
45
+ },
46
+ isEnabled(account) {
47
+ return account.enabled;
48
+ }
49
+ };
50
+ }
51
+
52
+ // src/adapters/gateway.ts
53
+ function createGatewayAdapter(getOrchestrator2) {
54
+ return {
55
+ async startAccount(ctx) {
56
+ const orchestrator2 = getOrchestrator2();
57
+ if (!orchestrator2) {
58
+ throw new Error("Orchestrator not initialized. Ensure setRuntime was called.");
59
+ }
60
+ ctx.log?.info(`Starting account "${ctx.accountId}"`);
61
+ await orchestrator2.start(ctx.account);
62
+ ctx.log?.info(`Account "${ctx.accountId}" started successfully`);
63
+ },
64
+ async stopAccount(ctx) {
65
+ const orchestrator2 = getOrchestrator2();
66
+ if (!orchestrator2) {
67
+ throw new Error("Orchestrator not initialized");
68
+ }
69
+ ctx.log?.info(`Stopping account "${ctx.accountId}"`);
70
+ await orchestrator2.stop(ctx.accountId);
71
+ }
72
+ };
73
+ }
74
+
75
+ // src/adapters/status.ts
76
+ function createStatusAdapter(getOrchestrator2, _runtime) {
77
+ return {
78
+ async probeAccount(params) {
79
+ const orchestrator2 = getOrchestrator2();
80
+ if (!orchestrator2) {
81
+ return {
82
+ ok: false,
83
+ status: "unhealthy",
84
+ wsConnected: false,
85
+ error: "Orchestrator not initialized"
86
+ };
87
+ }
88
+ const state = orchestrator2.getAccountState(params.account.id);
89
+ if (!state) {
90
+ return {
91
+ ok: false,
92
+ status: "unhealthy",
93
+ wsConnected: false,
94
+ error: "Account not started"
95
+ };
96
+ }
97
+ try {
98
+ const healthResult = await state.probe.runHealthChecks();
99
+ const wsConnected = state.wsClient?.isConnected() ?? false;
100
+ let status = "healthy";
101
+ switch (healthResult.status) {
102
+ case "healthy":
103
+ status = wsConnected ? "healthy" : "degraded";
104
+ break;
105
+ case "degraded":
106
+ status = "degraded";
107
+ break;
108
+ case "unhealthy":
109
+ status = "unhealthy";
110
+ break;
111
+ }
112
+ const checks = {};
113
+ for (const [name, result] of healthResult.checks) {
114
+ checks[name] = {
115
+ passed: result.passed,
116
+ message: result.message ?? "",
117
+ duration: result.duration
118
+ };
119
+ }
120
+ return {
121
+ ok: status !== "unhealthy",
122
+ status,
123
+ wsConnected,
124
+ uptime: Date.now() - state.startTime,
125
+ checks
126
+ };
127
+ } catch (error) {
128
+ return {
129
+ ok: false,
130
+ status: "unhealthy",
131
+ wsConnected: false,
132
+ error: error.message
133
+ };
134
+ }
135
+ },
136
+ async buildChannelSummary(params) {
137
+ const orchestrator2 = getOrchestrator2();
138
+ const accountId = params.account.id;
139
+ const state = orchestrator2?.getAccountState(accountId);
140
+ const runningIds = orchestrator2?.getRunningAccountIds() ?? [];
141
+ return {
142
+ accountId,
143
+ status: state?.status ?? "stopped",
144
+ wsConnected: state?.wsClient?.isConnected() ?? false,
145
+ isDefault: accountId === params.defaultAccountId,
146
+ running: runningIds.includes(accountId),
147
+ uptime: state ? Date.now() - state.startTime : 0
148
+ };
149
+ }
150
+ };
151
+ }
152
+
153
+ // src/adapters/directory.ts
154
+ function createDirectoryAdapter(getOrchestrator2) {
155
+ return {
156
+ async listPeers(params) {
157
+ const orchestrator2 = getOrchestrator2();
158
+ if (!orchestrator2) {
159
+ return [];
160
+ }
161
+ const accountId = params.accountId ?? "default";
162
+ const accountManager = orchestrator2.getAccountManager(accountId);
163
+ if (!accountManager) {
164
+ return [];
165
+ }
166
+ const activeAccounts = accountManager.getActiveAccounts();
167
+ let entries = activeAccounts.map((account) => ({
168
+ kind: "user",
169
+ id: account.deviceId,
170
+ name: account.deviceInfo.name || account.deviceId,
171
+ handle: account.deviceId,
172
+ raw: account
173
+ }));
174
+ if (params.query) {
175
+ const q = params.query.toLowerCase();
176
+ entries = entries.filter(
177
+ (e) => e.id.toLowerCase().includes(q) || (e.name?.toLowerCase().includes(q) ?? false)
178
+ );
179
+ }
180
+ if (params.limit != null && params.limit > 0) {
181
+ entries = entries.slice(0, params.limit);
182
+ }
183
+ return entries;
184
+ }
185
+ };
186
+ }
187
+
188
+ // src/adapters/messaging.ts
189
+ var PREFIX = "lingyao:";
190
+ function createMessagingAdapter() {
191
+ return {
192
+ normalizeTarget(raw) {
193
+ if (!raw || typeof raw !== "string") {
194
+ return void 0;
195
+ }
196
+ const target = raw.startsWith(PREFIX) ? raw.slice(PREFIX.length) : raw;
197
+ if (!target) {
198
+ return void 0;
199
+ }
200
+ return target;
201
+ },
202
+ resolveSessionTarget(params) {
203
+ return `${PREFIX}${params.id}`;
204
+ },
205
+ inferTargetChatType(_params) {
206
+ return "direct";
207
+ }
208
+ };
209
+ }
210
+
211
+ // src/adapters/outbound.ts
212
+ function createOutboundAdapter(getOrchestrator2) {
213
+ return {
214
+ deliveryMode: "direct",
215
+ async sendText(ctx) {
216
+ const orchestrator2 = getOrchestrator2();
217
+ if (!orchestrator2) {
218
+ throw new Error("Orchestrator not initialized");
219
+ }
220
+ const accountId = ctx.accountId ?? "default";
221
+ const sent = orchestrator2.sendNotification(
222
+ accountId,
223
+ ctx.to,
224
+ { title: "OpenClaw", body: ctx.text }
225
+ );
226
+ if (!sent) {
227
+ throw new Error(
228
+ `Failed to send text to device "${ctx.to}" on account "${accountId}": not connected`
229
+ );
230
+ }
231
+ return {
232
+ channel: "lingyao",
233
+ messageId: `out_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
234
+ chatId: ctx.to,
235
+ timestamp: Date.now()
236
+ };
237
+ },
238
+ async sendPayload(ctx) {
239
+ const orchestrator2 = getOrchestrator2();
240
+ if (!orchestrator2) {
241
+ throw new Error("Orchestrator not initialized");
242
+ }
243
+ const accountId = ctx.accountId ?? "default";
244
+ const payload = ctx.payload;
245
+ const sent = orchestrator2.sendNotification(
246
+ accountId,
247
+ ctx.to,
248
+ payload
249
+ );
250
+ if (!sent) {
251
+ throw new Error(
252
+ `Failed to send payload to device "${ctx.to}" on account "${accountId}": not connected`
253
+ );
254
+ }
255
+ return {
256
+ channel: "lingyao",
257
+ messageId: `out_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
258
+ chatId: ctx.to,
259
+ timestamp: Date.now()
260
+ };
261
+ },
262
+ resolveTarget(params) {
263
+ const raw = params.to;
264
+ if (!raw || typeof raw !== "string" || raw.trim().length === 0) {
265
+ return { ok: false, error: new Error("Target deviceId is empty or missing") };
266
+ }
267
+ return { ok: true, to: raw.trim() };
268
+ }
269
+ };
270
+ }
271
+
272
+ // src/orchestrator.ts
273
+ import { hostname } from "os";
274
+
275
+ // src/types.ts
276
+ var LINGYAO_SERVER_URL = "https://api.lingyao.live";
277
+ var MessageType = /* @__PURE__ */ ((MessageType2) => {
278
+ MessageType2["SYNC_DIARY"] = "sync_diary";
279
+ MessageType2["SYNC_MEMORY"] = "sync_memory";
280
+ MessageType2["SYNC_ACK"] = "sync_ack";
281
+ MessageType2["NOTIFY_TEXT"] = "notify_text";
282
+ MessageType2["NOTIFY_ACTION"] = "notify_action";
283
+ MessageType2["HEARTBEAT"] = "heartbeat";
284
+ MessageType2["PAIRING_REQUEST"] = "pairing_request";
285
+ MessageType2["PAIRING_CONFIRM"] = "pairing_confirm";
286
+ return MessageType2;
287
+ })(MessageType || {});
288
+
289
+ // src/server-client.ts
290
+ import axios from "axios";
291
+ function isAxiosError(error) {
292
+ return typeof error === "object" && error !== null && "isAxiosError" in error && error.isAxiosError === true;
293
+ }
294
+ var ServerHttpClient = class {
295
+ runtime;
296
+ config;
297
+ axiosInstance;
298
+ gatewayToken = null;
299
+ webhookSecret = null;
300
+ tokenExpiresAt = 0;
301
+ heartbeatInterval = 3e4;
302
+ heartbeatTimer = null;
303
+ gatewayId;
304
+ isRegistered = false;
305
+ isConnecting = false;
306
+ storagePrefix;
307
+ constructor(runtime, gatewayId, serverConfig = {}, storagePrefix = "lingyao") {
308
+ this.runtime = runtime;
309
+ this.gatewayId = gatewayId;
310
+ this.storagePrefix = storagePrefix;
311
+ this.config = {
312
+ baseURL: serverConfig.baseURL || "https://api.lingyao.live",
313
+ apiBase: serverConfig.apiBase || "/v1",
314
+ timeout: serverConfig.timeout || 3e4,
315
+ connectionTimeout: serverConfig.connectionTimeout || 5e3
316
+ };
317
+ this.axiosInstance = axios.create({
318
+ baseURL: this.config.baseURL + this.config.apiBase,
319
+ timeout: this.config.timeout,
320
+ headers: {
321
+ "Content-Type": "application/json"
322
+ }
323
+ });
324
+ this.axiosInstance.interceptors.request.use(
325
+ (config) => {
326
+ if (this.gatewayToken && config.headers) {
327
+ config.headers.Authorization = `Bearer ${this.gatewayToken}`;
328
+ }
329
+ return config;
330
+ },
331
+ (error) => Promise.reject(error)
332
+ );
333
+ this.axiosInstance.interceptors.response.use(
334
+ (response) => response,
335
+ async (error) => {
336
+ if (isAxiosError(error)) {
337
+ if (error.response?.status === 401) {
338
+ this.runtime.logger.warn("Token expired, attempting to re-register...");
339
+ await this.register();
340
+ }
341
+ }
342
+ return Promise.reject(error);
343
+ }
344
+ );
345
+ }
346
+ /**
347
+ * 注册 Gateway 到服务器
348
+ */
349
+ async register(capabilities = {}) {
350
+ if (this.isConnecting) {
351
+ throw new Error("Registration already in progress");
352
+ }
353
+ this.isConnecting = true;
354
+ try {
355
+ this.runtime.logger.info(`Registering gateway ${this.gatewayId} to server...`);
356
+ const response = await this.axiosInstance.post(
357
+ "/gateway/register",
358
+ {
359
+ gatewayId: this.gatewayId,
360
+ version: "0.1.0",
361
+ capabilities: {
362
+ websocket: false,
363
+ compression: false,
364
+ ...capabilities
365
+ }
366
+ }
367
+ );
368
+ const data = response.data;
369
+ this.gatewayToken = data.gatewayToken;
370
+ this.webhookSecret = data.webhookSecret;
371
+ this.tokenExpiresAt = data.expiresAt;
372
+ this.heartbeatInterval = data.serverConfig.heartbeatInterval;
373
+ this.isRegistered = true;
374
+ await this.runtime.storage.set(this.storageKey("gatewayToken"), this.gatewayToken);
375
+ await this.runtime.storage.set(this.storageKey("webhookSecret"), this.webhookSecret);
376
+ await this.runtime.storage.set(this.storageKey("tokenExpiresAt"), this.tokenExpiresAt);
377
+ await this.runtime.storage.set(this.storageKey("serverConfig"), data.serverConfig);
378
+ this.runtime.logger.info("Gateway registered successfully", {
379
+ expiresAt: new Date(this.tokenExpiresAt).toISOString(),
380
+ heartbeatInterval: this.heartbeatInterval
381
+ });
382
+ this.startHeartbeat();
383
+ return data;
384
+ } catch (error) {
385
+ if (isAxiosError(error)) {
386
+ const axiosError = error;
387
+ const status = axiosError.response?.status;
388
+ const data = axiosError.response?.data;
389
+ if (status === 409) {
390
+ throw new Error("Gateway already registered");
391
+ } else if (status === 400) {
392
+ throw new Error(`Invalid request: ${data?.details || "Unknown error"}`);
393
+ }
394
+ throw new Error(`Registration failed: ${axiosError.message}`);
395
+ }
396
+ throw error;
397
+ } finally {
398
+ this.isConnecting = false;
399
+ }
400
+ }
401
+ /**
402
+ * 发送心跳
403
+ */
404
+ async heartbeat(status = "online", activeConnections = 0) {
405
+ if (!this.isRegistered || !this.gatewayToken) {
406
+ throw new Error("Gateway not registered");
407
+ }
408
+ try {
409
+ const response = await this.axiosInstance.post(
410
+ "/gateway/heartbeat",
411
+ {
412
+ timestamp: Date.now(),
413
+ status,
414
+ activeConnections
415
+ }
416
+ );
417
+ return response.data;
418
+ } catch (error) {
419
+ if (isAxiosError(error)) {
420
+ const axiosError = error;
421
+ this.runtime.logger.error("Heartbeat failed", {
422
+ status: axiosError.response?.status,
423
+ data: axiosError.response?.data
424
+ });
425
+ }
426
+ throw error;
427
+ }
428
+ }
429
+ /**
430
+ * 发送消息到灵爻 App
431
+ */
432
+ async sendMessage(deviceId, messageType, payload, options) {
433
+ if (!this.isRegistered || !this.gatewayToken) {
434
+ throw new Error("Gateway not registered");
435
+ }
436
+ const messageId = this.generateMessageId();
437
+ try {
438
+ const response = await this.axiosInstance.post(
439
+ "/gateway/messages",
440
+ {
441
+ deviceId,
442
+ message: {
443
+ id: messageId,
444
+ type: messageType,
445
+ timestamp: Date.now(),
446
+ payload
447
+ },
448
+ options: options || {}
449
+ }
450
+ );
451
+ this.runtime.logger.debug("Message sent", {
452
+ messageId,
453
+ deviceId,
454
+ status: response.data.status
455
+ });
456
+ return response.data;
457
+ } catch (error) {
458
+ if (isAxiosError(error)) {
459
+ const axiosError = error;
460
+ const status = axiosError.response?.status;
461
+ const data = axiosError.response?.data;
462
+ if (status === 404) {
463
+ throw new Error(`Device not found: ${deviceId}`);
464
+ } else if (status === 429) {
465
+ throw new Error("Message queue full, please retry later");
466
+ }
467
+ throw new Error(`Send message failed: ${data?.error || axiosError.message}`);
468
+ }
469
+ throw error;
470
+ }
471
+ }
472
+ /**
473
+ * 启动心跳循环
474
+ */
475
+ startHeartbeat() {
476
+ if (this.heartbeatTimer) {
477
+ clearInterval(this.heartbeatTimer);
478
+ }
479
+ this.heartbeatTimer = setInterval(
480
+ async () => {
481
+ try {
482
+ await this.heartbeat();
483
+ } catch (error) {
484
+ this.runtime.logger.error("Heartbeat error", error);
485
+ }
486
+ },
487
+ this.heartbeatInterval
488
+ );
489
+ this.runtime.logger.info("Heartbeat started", {
490
+ interval: this.heartbeatInterval
491
+ });
492
+ }
493
+ /**
494
+ * 停止心跳循环
495
+ */
496
+ stopHeartbeat() {
497
+ if (this.heartbeatTimer) {
498
+ clearInterval(this.heartbeatTimer);
499
+ this.heartbeatTimer = null;
500
+ this.runtime.logger.info("Heartbeat stopped");
501
+ }
502
+ }
503
+ /**
504
+ * 获取 Webhook Secret
505
+ */
506
+ getWebhookSecret() {
507
+ return this.webhookSecret;
508
+ }
509
+ /**
510
+ * 获取 Gateway Token
511
+ */
512
+ getGatewayToken() {
513
+ return this.gatewayToken;
514
+ }
515
+ /**
516
+ * 检查 Token 是否即将过期
517
+ */
518
+ isTokenExpiringSoon(thresholdMs = 7 * 24 * 60 * 60 * 1e3) {
519
+ return this.tokenExpiresAt - Date.now() < thresholdMs;
520
+ }
521
+ /**
522
+ * 检查是否已注册
523
+ */
524
+ isReady() {
525
+ return this.isRegistered && !!this.gatewayToken;
526
+ }
527
+ /**
528
+ * 从存储恢复会话
529
+ */
530
+ async restoreFromStorage() {
531
+ try {
532
+ const token = await this.runtime.storage.get(this.storageKey("gatewayToken"));
533
+ const secret = await this.runtime.storage.get(this.storageKey("webhookSecret"));
534
+ const expiresAt = await this.runtime.storage.get(this.storageKey("tokenExpiresAt"));
535
+ const serverConfig = await this.runtime.storage.get(this.storageKey("serverConfig"));
536
+ if (token && secret && expiresAt && serverConfig) {
537
+ if (expiresAt > Date.now()) {
538
+ this.gatewayToken = token;
539
+ this.webhookSecret = secret;
540
+ this.tokenExpiresAt = expiresAt;
541
+ this.heartbeatInterval = serverConfig.heartbeatInterval;
542
+ this.isRegistered = true;
543
+ this.startHeartbeat();
544
+ this.runtime.logger.info("Session restored from storage", {
545
+ expiresAt: new Date(this.tokenExpiresAt).toISOString()
546
+ });
547
+ return true;
548
+ } else {
549
+ this.runtime.logger.warn("Stored token expired, need to re-register");
550
+ }
551
+ }
552
+ } catch (error) {
553
+ this.runtime.logger.error("Failed to restore session from storage", error);
554
+ }
555
+ return false;
556
+ }
557
+ /**
558
+ * Build a namespaced storage key for multi-account support
559
+ */
560
+ storageKey(name) {
561
+ return `${this.storagePrefix}:${name}`;
562
+ }
563
+ /**
564
+ * 生成消息 ID
565
+ */
566
+ generateMessageId() {
567
+ return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
568
+ }
569
+ };
570
+
571
+ // src/websocket-client.ts
572
+ import WebSocket from "ws";
573
+ var LingyaoWSClient = class {
574
+ config;
575
+ ws = null;
576
+ state = "disconnected";
577
+ connectionId = null;
578
+ heartbeatTimer = null;
579
+ reconnectTimer = null;
580
+ messageHandlers = /* @__PURE__ */ new Map();
581
+ logger;
582
+ constructor(runtime, config) {
583
+ this.logger = runtime.logger;
584
+ this.config = { ...config };
585
+ this.registerMessageHandler("registered" /* REGISTERED */, this.handleRegistered.bind(this));
586
+ this.registerMessageHandler("heartbeat_ack" /* HEARTBEAT_ACK */, this.handleHeartbeatAck.bind(this));
587
+ this.registerMessageHandler("message_delivered" /* MESSAGE_DELIVERED */, this.handleMessageDelivered.bind(this));
588
+ this.registerMessageHandler("message_failed" /* MESSAGE_FAILED */, this.handleMessageFailed.bind(this));
589
+ this.registerMessageHandler("app_message" /* APP_MESSAGE */, this.handleAppMessage.bind(this));
590
+ this.registerMessageHandler("error" /* ERROR */, this.handleError.bind(this));
591
+ }
592
+ /**
593
+ * 连接到服务器
594
+ */
595
+ async connect() {
596
+ if (this.state === "connecting" || this.state === "connected") {
597
+ this.logger.warn("WebSocket already connecting or connected");
598
+ return;
599
+ }
600
+ this.state = "connecting";
601
+ this.emitEvent({ type: "disconnected", code: 0, reason: "Reconnecting" });
602
+ try {
603
+ this.logger.info(`Connecting to Lingyao server: ${this.config.url}`);
604
+ const wsUrl = this.config.token ? `${this.config.url}?token=${encodeURIComponent(this.config.token)}` : this.config.url;
605
+ this.ws = new WebSocket(wsUrl, {
606
+ headers: {
607
+ "X-Gateway-ID": this.config.gatewayId
608
+ }
609
+ });
610
+ this.setupWebSocketHandlers();
611
+ } catch (error) {
612
+ this.state = "error";
613
+ this.emitEvent({ type: "error", error });
614
+ this.scheduleReconnect();
615
+ }
616
+ }
617
+ /**
618
+ * 设置 WebSocket 事件处理器
619
+ */
620
+ setupWebSocketHandlers() {
621
+ if (!this.ws) return;
622
+ this.ws.on("open", () => {
623
+ this.handleOpen();
624
+ });
625
+ this.ws.on("message", async (data) => {
626
+ await this.handleMessage(data);
627
+ });
628
+ this.ws.on("error", (error) => {
629
+ this.handleErrorEvent(error);
630
+ });
631
+ this.ws.on("close", (code, reason) => {
632
+ this.handleClose(code, reason.toString());
633
+ });
634
+ }
635
+ /**
636
+ * 处理连接打开
637
+ */
638
+ handleOpen() {
639
+ this.state = "connected";
640
+ this.connectionId = this.generateConnectionId();
641
+ this.logger.info("WebSocket connected to Lingyao server", {
642
+ connectionId: this.connectionId
643
+ });
644
+ this.emitEvent({
645
+ type: "connected",
646
+ connectionId: this.connectionId
647
+ });
648
+ this.sendRegister();
649
+ this.startHeartbeat();
650
+ if (this.reconnectTimer) {
651
+ clearTimeout(this.reconnectTimer);
652
+ this.reconnectTimer = null;
653
+ }
654
+ }
655
+ /**
656
+ * 处理接收消息
657
+ */
658
+ async handleMessage(data) {
659
+ try {
660
+ const message = JSON.parse(data.toString());
661
+ this.logger.debug("Received message from server", { type: message.type });
662
+ const handler = this.messageHandlers.get(message.type);
663
+ if (handler) {
664
+ handler(message);
665
+ } else {
666
+ this.logger.warn("No handler for message type", { type: message.type });
667
+ }
668
+ this.emitEvent({ type: "message", message });
669
+ } catch (error) {
670
+ this.logger.error("Error handling message", error);
671
+ }
672
+ }
673
+ /**
674
+ * 处理连接错误
675
+ */
676
+ handleErrorEvent(error) {
677
+ this.logger.error("WebSocket error", error);
678
+ this.state = "error";
679
+ this.emitEvent({ type: "error", error });
680
+ }
681
+ /**
682
+ * 处理连接关闭
683
+ */
684
+ handleClose(code, reason) {
685
+ this.logger.warn("WebSocket connection closed", { code, reason });
686
+ this.state = "disconnected";
687
+ this.connectionId = null;
688
+ this.stopHeartbeat();
689
+ this.emitEvent({ type: "disconnected", code, reason });
690
+ if (code !== 1e3) {
691
+ this.scheduleReconnect();
692
+ }
693
+ }
694
+ /**
695
+ * 发送注册消息
696
+ */
697
+ sendRegister() {
698
+ const message = {
699
+ type: "register" /* REGISTER */,
700
+ id: this.generateMessageId(),
701
+ timestamp: Date.now(),
702
+ payload: {
703
+ gatewayId: this.config.gatewayId,
704
+ version: "0.2.0",
705
+ capabilities: {
706
+ websocket: true,
707
+ compression: false,
708
+ maxMessageSize: 1048576
709
+ // 1MB
710
+ }
711
+ }
712
+ };
713
+ this.send(message);
714
+ }
715
+ /**
716
+ * 处理注册响应
717
+ */
718
+ handleRegistered(message) {
719
+ this.logger.info("Gateway registered to Lingyao server", {
720
+ messageId: message.id
721
+ });
722
+ }
723
+ /**
724
+ * 发送心跳
725
+ */
726
+ sendHeartbeat() {
727
+ const message = {
728
+ type: "heartbeat" /* HEARTBEAT */,
729
+ id: this.generateMessageId(),
730
+ timestamp: Date.now(),
731
+ payload: {
732
+ timestamp: Date.now(),
733
+ status: "online"
734
+ }
735
+ };
736
+ this.send(message);
737
+ }
738
+ /**
739
+ * 处理心跳确认
740
+ */
741
+ handleHeartbeatAck(_message) {
742
+ this.logger.debug("Heartbeat acknowledged");
743
+ }
744
+ /**
745
+ * 启动心跳
746
+ */
747
+ startHeartbeat() {
748
+ if (this.heartbeatTimer) {
749
+ clearInterval(this.heartbeatTimer);
750
+ }
751
+ this.heartbeatTimer = setInterval(() => {
752
+ if (this.state === "connected") {
753
+ this.sendHeartbeat();
754
+ }
755
+ }, this.config.heartbeatInterval);
756
+ }
757
+ /**
758
+ * 停止心跳
759
+ */
760
+ stopHeartbeat() {
761
+ if (this.heartbeatTimer) {
762
+ clearInterval(this.heartbeatTimer);
763
+ this.heartbeatTimer = null;
764
+ }
765
+ }
766
+ /**
767
+ * 安排重连
768
+ */
769
+ scheduleReconnect() {
770
+ if (this.reconnectTimer) {
771
+ return;
772
+ }
773
+ this.logger.info(`Scheduling reconnect in ${this.config.reconnectInterval}ms`);
774
+ this.reconnectTimer = setTimeout(() => {
775
+ this.reconnectTimer = null;
776
+ this.connect();
777
+ }, this.config.reconnectInterval);
778
+ }
779
+ /**
780
+ * 发送消息到服务器
781
+ */
782
+ send(message) {
783
+ if (!this.ws || this.state !== "connected") {
784
+ throw new Error("WebSocket not connected");
785
+ }
786
+ try {
787
+ this.ws.send(JSON.stringify(message));
788
+ this.logger.debug("Sent message to server", { type: message.type });
789
+ } catch (error) {
790
+ this.logger.error("Failed to send message", error);
791
+ throw error;
792
+ }
793
+ }
794
+ /**
795
+ * 发送通知到鸿蒙 App
796
+ */
797
+ sendNotification(deviceId, notification) {
798
+ const message = {
799
+ type: "send_message" /* SEND_MESSAGE */,
800
+ id: this.generateMessageId(),
801
+ timestamp: Date.now(),
802
+ payload: {
803
+ deviceId,
804
+ message: {
805
+ id: this.generateMessageId(),
806
+ type: "notify_action",
807
+ timestamp: Date.now(),
808
+ payload: notification
809
+ }
810
+ }
811
+ };
812
+ this.send(message);
813
+ }
814
+ /**
815
+ * 处理 App 消息
816
+ */
817
+ handleAppMessage(message) {
818
+ if (message.type !== "app_message" /* APP_MESSAGE */) return;
819
+ const appMessage = message;
820
+ this.logger.info("Received message from App", {
821
+ deviceId: appMessage.payload.deviceId,
822
+ messageType: appMessage.payload.message.type
823
+ });
824
+ if (this.config.messageHandler) {
825
+ Promise.resolve(this.config.messageHandler(appMessage)).catch((error) => {
826
+ this.logger.error("Error handling App message", error);
827
+ });
828
+ }
829
+ }
830
+ /**
831
+ * 处理消息发送成功
832
+ */
833
+ handleMessageDelivered(message) {
834
+ this.logger.debug("Message delivered successfully", {
835
+ messageId: message.id
836
+ });
837
+ }
838
+ /**
839
+ * 处理消息发送失败
840
+ */
841
+ handleMessageFailed(message) {
842
+ this.logger.warn("Message delivery failed", {
843
+ messageId: message.id
844
+ });
845
+ }
846
+ /**
847
+ * 处理服务器错误
848
+ */
849
+ handleError(message) {
850
+ this.logger.error("Server error", message);
851
+ }
852
+ /**
853
+ * 注册消息处理器
854
+ */
855
+ registerMessageHandler(type, handler) {
856
+ this.messageHandlers.set(type, handler);
857
+ }
858
+ /**
859
+ * 发送事件
860
+ */
861
+ emitEvent(event) {
862
+ if (this.config.eventHandler) {
863
+ this.config.eventHandler(event);
864
+ }
865
+ }
866
+ /**
867
+ * 断开连接
868
+ */
869
+ disconnect() {
870
+ this.logger.info("Disconnecting WebSocket from Lingyao server");
871
+ this.stopHeartbeat();
872
+ if (this.reconnectTimer) {
873
+ clearTimeout(this.reconnectTimer);
874
+ this.reconnectTimer = null;
875
+ }
876
+ if (this.ws) {
877
+ this.ws.close(1e3, "Client disconnect");
878
+ this.ws = null;
879
+ }
880
+ this.state = "disconnected";
881
+ this.connectionId = null;
882
+ }
883
+ /**
884
+ * 获取连接状态
885
+ */
886
+ getState() {
887
+ return this.state;
888
+ }
889
+ /**
890
+ * 获取连接 ID
891
+ */
892
+ getConnectionId() {
893
+ return this.connectionId;
894
+ }
895
+ /**
896
+ * 是否已连接
897
+ */
898
+ isConnected() {
899
+ return this.state === "connected";
900
+ }
901
+ /**
902
+ * 生成消息 ID
903
+ */
904
+ generateMessageId() {
905
+ return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
906
+ }
907
+ /**
908
+ * 生成连接 ID
909
+ */
910
+ generateConnectionId() {
911
+ return `conn_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
912
+ }
913
+ };
914
+
915
+ // src/accounts.ts
916
+ var STORAGE_KEY_ACCOUNTS = "lingyao:accounts";
917
+ var STORAGE_KEY_PENDING_PAIRINGS = "lingyao:pending_pairings";
918
+ var AccountManager = class {
919
+ runtime;
920
+ accounts = /* @__PURE__ */ new Map();
921
+ pendingPairings = /* @__PURE__ */ new Map();
922
+ constructor(runtime) {
923
+ this.runtime = runtime;
924
+ }
925
+ /**
926
+ * Initialize account manager from storage
927
+ */
928
+ async initialize() {
929
+ try {
930
+ const stored = await this.runtime.storage.get(STORAGE_KEY_ACCOUNTS);
931
+ if (stored && Array.isArray(stored)) {
932
+ for (const account of stored) {
933
+ this.accounts.set(account.deviceId, account);
934
+ }
935
+ }
936
+ const pending = await this.runtime.storage.get(STORAGE_KEY_PENDING_PAIRINGS);
937
+ if (pending && Array.isArray(pending)) {
938
+ const now = Date.now();
939
+ for (const session of pending) {
940
+ if (session.expiresAt > now) {
941
+ this.pendingPairings.set(session.code, session);
942
+ }
943
+ }
944
+ }
945
+ this.runtime.logger.info(
946
+ `AccountManager initialized: ${this.accounts.size} accounts, ${this.pendingPairings.size} pending pairings`
947
+ );
948
+ } catch (error) {
949
+ this.runtime.logger.error("Failed to initialize AccountManager", error);
950
+ }
951
+ }
952
+ /**
953
+ * Get account by device ID
954
+ */
955
+ getAccount(deviceId) {
956
+ return this.accounts.get(deviceId);
957
+ }
958
+ /**
959
+ * Get account by device token
960
+ */
961
+ getAccountByToken(token) {
962
+ for (const account of this.accounts.values()) {
963
+ if (account.deviceToken.token === token) {
964
+ return account;
965
+ }
966
+ }
967
+ return void 0;
968
+ }
969
+ /**
970
+ * Get all active accounts
971
+ */
972
+ getActiveAccounts() {
973
+ return Array.from(this.accounts.values()).filter(
974
+ (acc) => acc.status === "active"
975
+ );
976
+ }
977
+ /**
978
+ * Create a new pairing session
979
+ */
980
+ async createPairingSession(code, expiresAt) {
981
+ const session = {
982
+ code,
983
+ createdAt: Date.now(),
984
+ expiresAt
985
+ };
986
+ this.pendingPairings.set(code, session);
987
+ await this.savePendingPairings();
988
+ }
989
+ /**
990
+ * Get pairing session by code
991
+ */
992
+ getPairingSession(code) {
993
+ return this.pendingPairings.get(code);
994
+ }
995
+ /**
996
+ * Remove a pending pairing (e.g. expired or cancelled)
997
+ */
998
+ async deletePendingPairing(code) {
999
+ if (!this.pendingPairings.delete(code)) {
1000
+ return;
1001
+ }
1002
+ await this.savePendingPairings();
1003
+ }
1004
+ /**
1005
+ * Confirm pairing and create account
1006
+ */
1007
+ async confirmPairing(pairingCode, deviceToken, deviceInfo) {
1008
+ const session = this.pendingPairings.get(pairingCode);
1009
+ if (!session) {
1010
+ return null;
1011
+ }
1012
+ const now = Date.now();
1013
+ if (session.expiresAt < now) {
1014
+ this.pendingPairings.delete(pairingCode);
1015
+ await this.savePendingPairings();
1016
+ return null;
1017
+ }
1018
+ const account = {
1019
+ deviceId: deviceToken.deviceId,
1020
+ deviceInfo,
1021
+ deviceToken,
1022
+ pairedAt: now,
1023
+ lastSeenAt: now,
1024
+ status: "active"
1025
+ };
1026
+ this.accounts.set(deviceToken.deviceId, account);
1027
+ this.pendingPairings.delete(pairingCode);
1028
+ await Promise.all([
1029
+ this.saveAccounts(),
1030
+ this.savePendingPairings()
1031
+ ]);
1032
+ this.runtime.logger.info(
1033
+ `Account paired: ${deviceToken.deviceId} (${deviceInfo.name})`
1034
+ );
1035
+ return account;
1036
+ }
1037
+ /**
1038
+ * Update account's last seen timestamp
1039
+ */
1040
+ async updateLastSeen(deviceId) {
1041
+ const account = this.accounts.get(deviceId);
1042
+ if (account) {
1043
+ account.lastSeenAt = Date.now();
1044
+ await this.saveAccounts();
1045
+ }
1046
+ }
1047
+ /**
1048
+ * Revoke an account
1049
+ */
1050
+ async revokeAccount(deviceId) {
1051
+ const account = this.accounts.get(deviceId);
1052
+ if (!account) {
1053
+ return false;
1054
+ }
1055
+ account.status = "revoked";
1056
+ await this.saveAccounts();
1057
+ this.runtime.logger.info(`Account revoked: ${deviceId}`);
1058
+ return true;
1059
+ }
1060
+ /**
1061
+ * Refresh device token
1062
+ */
1063
+ async refreshDeviceToken(deviceId, newToken) {
1064
+ const account = this.accounts.get(deviceId);
1065
+ if (!account) {
1066
+ return false;
1067
+ }
1068
+ account.deviceToken = newToken;
1069
+ await this.saveAccounts();
1070
+ this.runtime.logger.info(`Token refreshed for: ${deviceId}`);
1071
+ return true;
1072
+ }
1073
+ /**
1074
+ * Clean up expired accounts
1075
+ */
1076
+ async cleanupExpired() {
1077
+ const now = Date.now();
1078
+ const expired = [];
1079
+ for (const [deviceId, account] of this.accounts.entries()) {
1080
+ if (account.deviceToken.expiresAt < now) {
1081
+ expired.push(deviceId);
1082
+ }
1083
+ }
1084
+ for (const deviceId of expired) {
1085
+ this.accounts.delete(deviceId);
1086
+ }
1087
+ if (expired.length > 0) {
1088
+ await this.saveAccounts();
1089
+ this.runtime.logger.info(`Cleaned up ${expired.length} expired accounts`);
1090
+ }
1091
+ }
1092
+ /**
1093
+ * Save accounts to storage
1094
+ */
1095
+ async saveAccounts() {
1096
+ const accounts = Array.from(this.accounts.values());
1097
+ await this.runtime.storage.set(STORAGE_KEY_ACCOUNTS, accounts);
1098
+ }
1099
+ /**
1100
+ * Save pending pairings to storage
1101
+ */
1102
+ async savePendingPairings() {
1103
+ const sessions = Array.from(this.pendingPairings.values());
1104
+ await this.runtime.storage.set(STORAGE_KEY_PENDING_PAIRINGS, sessions);
1105
+ }
1106
+ };
1107
+
1108
+ // src/crypto.ts
1109
+ import { createHmac, createHash, randomBytes, timingSafeEqual } from "crypto";
1110
+ var CryptoManager = class {
1111
+ /**
1112
+ * Sign a message with HMAC-SHA256
1113
+ */
1114
+ static signMessage(message, secret) {
1115
+ const messageString = this.prepareMessageForSigning(message);
1116
+ return createHmac("sha256", secret).update(messageString).digest("base64");
1117
+ }
1118
+ /**
1119
+ * Verify a message signature
1120
+ */
1121
+ static verifyMessage(message, signature, secret) {
1122
+ try {
1123
+ const expectedSignature = this.signMessage(message, secret);
1124
+ const expectedBuffer = Buffer.from(expectedSignature);
1125
+ const providedBuffer = Buffer.from(signature);
1126
+ if (expectedBuffer.length !== providedBuffer.length) {
1127
+ return false;
1128
+ }
1129
+ return timingSafeEqual(expectedBuffer, providedBuffer);
1130
+ } catch {
1131
+ return false;
1132
+ }
1133
+ }
1134
+ /**
1135
+ * Sign any data string
1136
+ */
1137
+ static sign(data, secret) {
1138
+ return createHmac("sha256", secret).update(data).digest("base64");
1139
+ }
1140
+ /**
1141
+ * Verify data signature
1142
+ */
1143
+ static verify(data, signature, secret) {
1144
+ try {
1145
+ const expectedSignature = this.sign(data, secret);
1146
+ const expectedBuffer = Buffer.from(expectedSignature);
1147
+ const providedBuffer = Buffer.from(signature);
1148
+ if (expectedBuffer.length !== providedBuffer.length) {
1149
+ return false;
1150
+ }
1151
+ return timingSafeEqual(expectedBuffer, providedBuffer);
1152
+ } catch {
1153
+ return false;
1154
+ }
1155
+ }
1156
+ /**
1157
+ * Generate a random nonce
1158
+ */
1159
+ static generateNonce() {
1160
+ return randomBytes(16).toString("hex");
1161
+ }
1162
+ /**
1163
+ * Hash data using SHA-256
1164
+ */
1165
+ static hash(data) {
1166
+ return createHash("sha256").update(data).digest("hex");
1167
+ }
1168
+ /**
1169
+ * Generate a cryptographically random ID
1170
+ */
1171
+ static generateId() {
1172
+ return randomBytes(16).toString("hex");
1173
+ }
1174
+ /**
1175
+ * Prepare message for signing by serializing in a deterministic way
1176
+ */
1177
+ static prepareMessageForSigning(message) {
1178
+ const parts = [
1179
+ message.id,
1180
+ message.type,
1181
+ message.timestamp.toString(),
1182
+ message.from,
1183
+ message.to,
1184
+ JSON.stringify(message.payload)
1185
+ ];
1186
+ return parts.join("|");
1187
+ }
1188
+ /**
1189
+ * Encrypt data (for future use)
1190
+ */
1191
+ static encrypt(data, _key) {
1192
+ const iv = randomBytes(16);
1193
+ return {
1194
+ encrypted: data,
1195
+ // Placeholder
1196
+ iv: iv.toString("hex")
1197
+ };
1198
+ }
1199
+ /**
1200
+ * Decrypt data (for future use)
1201
+ */
1202
+ static decrypt(encrypted, _key, _iv) {
1203
+ return encrypted;
1204
+ }
1205
+ /**
1206
+ * Derive a key from a password and salt
1207
+ */
1208
+ static deriveKey(password, salt) {
1209
+ return createHmac("sha256", salt).update(password).digest("hex");
1210
+ }
1211
+ /**
1212
+ * Generate a fingerprint for a device
1213
+ */
1214
+ static generateDeviceFingerprint(deviceInfo) {
1215
+ const data = JSON.stringify(deviceInfo, Object.keys(deviceInfo).sort());
1216
+ return this.hash(data).substring(0, 16);
1217
+ }
1218
+ /**
1219
+ * Verify message timestamp is within acceptable range
1220
+ */
1221
+ static verifyTimestamp(timestamp, maxDriftSeconds = 300) {
1222
+ const now = Date.now();
1223
+ const drift = Math.abs(now - timestamp);
1224
+ return drift <= maxDriftSeconds * 1e3;
1225
+ }
1226
+ /**
1227
+ * Check if a message is a replay attack
1228
+ */
1229
+ static checkReplay(messageId, seenMessages, _maxAge = 36e5) {
1230
+ return seenMessages.has(messageId);
1231
+ }
1232
+ /**
1233
+ * Mark a message as seen to prevent replay
1234
+ */
1235
+ static markMessageSeen(messageId, seenMessages, _maxAge = 36e5) {
1236
+ seenMessages.add(messageId);
1237
+ if (seenMessages.size > 1e4) {
1238
+ const entries = Array.from(seenMessages);
1239
+ for (let i = 0; i < entries.length / 2; i++) {
1240
+ seenMessages.delete(entries[i]);
1241
+ }
1242
+ }
1243
+ }
1244
+ };
1245
+
1246
+ // src/bot.ts
1247
+ var MessageProcessor = class {
1248
+ runtime;
1249
+ accountManager;
1250
+ messageQueue = /* @__PURE__ */ new Map();
1251
+ seenMessages = /* @__PURE__ */ new Set();
1252
+ messageHandler = null;
1253
+ maxQueueSize = 100;
1254
+ constructor(runtime, accountManager) {
1255
+ this.runtime = runtime;
1256
+ this.accountManager = accountManager;
1257
+ }
1258
+ /**
1259
+ * Initialize the message processor
1260
+ */
1261
+ async initialize() {
1262
+ this.runtime.logger.info("Message Processor initialized");
1263
+ }
1264
+ /**
1265
+ * Set the message handler for delivering messages to Agent
1266
+ */
1267
+ setMessageHandler(handler) {
1268
+ this.messageHandler = handler;
1269
+ }
1270
+ /**
1271
+ * Get the currently registered Agent message handler.
1272
+ */
1273
+ getMessageHandler() {
1274
+ return this.messageHandler;
1275
+ }
1276
+ /**
1277
+ * Deliver a normalized message directly to the registered Agent handler.
1278
+ */
1279
+ async deliverToAgent(message) {
1280
+ if (!this.messageHandler) {
1281
+ this.runtime.logger.warn("No message handler set, message not delivered to Agent");
1282
+ return;
1283
+ }
1284
+ await this.messageHandler(message);
1285
+ }
1286
+ /**
1287
+ * Process sync request from app
1288
+ */
1289
+ async processSync(deviceId, request) {
1290
+ const account = this.accountManager.getAccount(deviceId);
1291
+ if (!account) {
1292
+ return {
1293
+ processed: [],
1294
+ failed: request.messages.map((m) => ({
1295
+ id: m.id,
1296
+ error: "Device not found"
1297
+ }))
1298
+ };
1299
+ }
1300
+ const processed = [];
1301
+ const failed = [];
1302
+ for (const message of request.messages) {
1303
+ try {
1304
+ if (message.signature) {
1305
+ const isValid = CryptoManager.verifyMessage(
1306
+ message,
1307
+ message.signature,
1308
+ account.deviceToken.secret
1309
+ );
1310
+ if (!isValid) {
1311
+ failed.push({ id: message.id, error: "Invalid signature" });
1312
+ continue;
1313
+ }
1314
+ }
1315
+ if (this.isMessageSeen(message.id)) {
1316
+ this.runtime.logger.debug(`Duplicate message ignored: ${message.id}`);
1317
+ processed.push(message.id);
1318
+ continue;
1319
+ }
1320
+ if (!CryptoManager.verifyTimestamp(message.timestamp)) {
1321
+ failed.push({ id: message.id, error: "Invalid timestamp" });
1322
+ continue;
1323
+ }
1324
+ const result = await this.processMessage(deviceId, message);
1325
+ if (result.success) {
1326
+ processed.push(message.id);
1327
+ this.markMessageSeen(message.id);
1328
+ } else {
1329
+ failed.push({ id: message.id, error: result.error ?? "Processing failed" });
1330
+ }
1331
+ } catch (error) {
1332
+ this.runtime.logger.error(`Error processing message ${message.id}`, error);
1333
+ failed.push({ id: message.id, error: "Internal error" });
1334
+ }
1335
+ }
1336
+ return { processed, failed };
1337
+ }
1338
+ /**
1339
+ * Process a single message
1340
+ */
1341
+ async processMessage(deviceId, message) {
1342
+ this.runtime.logger.debug(
1343
+ `Processing message ${message.type} from ${deviceId}`
1344
+ );
1345
+ switch (message.type) {
1346
+ case "sync_diary" /* SYNC_DIARY */:
1347
+ return await this.processDiarySync(deviceId, message);
1348
+ case "sync_memory" /* SYNC_MEMORY */:
1349
+ return await this.processMemorySync(deviceId, message);
1350
+ case "sync_ack" /* SYNC_ACK */:
1351
+ return await this.processSyncAck(deviceId, message);
1352
+ case "heartbeat" /* HEARTBEAT */:
1353
+ return await this.processHeartbeat(deviceId, message);
1354
+ default:
1355
+ return { success: false, error: "Unknown message type" };
1356
+ }
1357
+ }
1358
+ /**
1359
+ * Process diary sync - Pass directly to Agent
1360
+ */
1361
+ async processDiarySync(deviceId, message) {
1362
+ try {
1363
+ const payload = message.payload;
1364
+ const agentMessage = {
1365
+ id: message.id,
1366
+ type: "diary",
1367
+ from: `lingyao:${deviceId}`,
1368
+ deviceId,
1369
+ content: payload.content,
1370
+ metadata: {
1371
+ diaryId: payload.diaryId,
1372
+ title: payload.title,
1373
+ emotion: payload.emotion,
1374
+ tags: payload.tags,
1375
+ mediaUrls: payload.mediaUrls,
1376
+ createdAt: payload.createdAt,
1377
+ updatedAt: payload.updatedAt
1378
+ },
1379
+ timestamp: message.timestamp
1380
+ };
1381
+ await this.deliverToAgent(agentMessage);
1382
+ this.runtime.logger.info(
1383
+ `Diary message passed to Agent: ${payload.diaryId} from ${deviceId}`
1384
+ );
1385
+ return { success: true };
1386
+ } catch (error) {
1387
+ this.runtime.logger.error("Diary sync error", error);
1388
+ return { success: false, error: "Failed to sync diary" };
1389
+ }
1390
+ }
1391
+ /**
1392
+ * Process memory sync - Pass directly to Agent
1393
+ */
1394
+ async processMemorySync(deviceId, message) {
1395
+ try {
1396
+ const payload = message.payload;
1397
+ const agentMessage = {
1398
+ id: message.id,
1399
+ type: "memory",
1400
+ from: `lingyao:${deviceId}`,
1401
+ deviceId,
1402
+ content: payload.content,
1403
+ metadata: {
1404
+ memoryId: payload.memoryId,
1405
+ memoryType: payload.type,
1406
+ importance: payload.importance,
1407
+ ...payload.metadata,
1408
+ timestamp: payload.timestamp
1409
+ },
1410
+ timestamp: message.timestamp
1411
+ };
1412
+ await this.deliverToAgent(agentMessage);
1413
+ this.runtime.logger.info(
1414
+ `Memory message passed to Agent: ${payload.memoryId} from ${deviceId}`
1415
+ );
1416
+ return { success: true };
1417
+ } catch (error) {
1418
+ this.runtime.logger.error("Memory sync error", error);
1419
+ return { success: false, error: "Failed to sync memory" };
1420
+ }
1421
+ }
1422
+ /**
1423
+ * Process sync acknowledgment
1424
+ */
1425
+ async processSyncAck(_deviceId, message) {
1426
+ try {
1427
+ this.runtime.logger.debug(`Sync ack received: ${message.id}`);
1428
+ return { success: true };
1429
+ } catch (error) {
1430
+ this.runtime.logger.error("Sync ack error", error);
1431
+ return { success: false, error: "Failed to process ack" };
1432
+ }
1433
+ }
1434
+ /**
1435
+ * Process heartbeat
1436
+ */
1437
+ async processHeartbeat(deviceId, _message) {
1438
+ try {
1439
+ await this.accountManager.updateLastSeen(deviceId);
1440
+ return { success: true };
1441
+ } catch (error) {
1442
+ this.runtime.logger.error("Heartbeat error", error);
1443
+ return { success: false, error: "Failed to process heartbeat" };
1444
+ }
1445
+ }
1446
+ /**
1447
+ * Poll for new messages (long-polling)
1448
+ */
1449
+ async pollMessages(deviceId, timeout = 3e4) {
1450
+ return new Promise((resolve) => {
1451
+ const startTime = Date.now();
1452
+ const checkInterval = 100;
1453
+ const checkForMessages = () => {
1454
+ const queue = this.messageQueue.get(deviceId);
1455
+ const messages = queue?.filter((m) => !m.delivered) ?? [];
1456
+ if (messages.length > 0) {
1457
+ for (const msg of messages) {
1458
+ msg.delivered = true;
1459
+ }
1460
+ resolve(messages.map((m) => m.message));
1461
+ return;
1462
+ }
1463
+ const elapsed = Date.now() - startTime;
1464
+ if (elapsed >= timeout) {
1465
+ resolve([]);
1466
+ return;
1467
+ }
1468
+ setTimeout(checkForMessages, checkInterval);
1469
+ };
1470
+ checkForMessages();
1471
+ });
1472
+ }
1473
+ /**
1474
+ * Queue a message for a device
1475
+ */
1476
+ queueMessage(deviceId, message) {
1477
+ let queue = this.messageQueue.get(deviceId);
1478
+ if (!queue) {
1479
+ queue = [];
1480
+ this.messageQueue.set(deviceId, queue);
1481
+ }
1482
+ if (queue.length >= this.maxQueueSize) {
1483
+ const oldestIndex = queue.findIndex((m) => !m.delivered);
1484
+ if (oldestIndex >= 0) {
1485
+ queue.splice(oldestIndex, 1);
1486
+ } else {
1487
+ this.runtime.logger.warn(
1488
+ `Message queue full for ${deviceId}, dropping message`
1489
+ );
1490
+ return false;
1491
+ }
1492
+ }
1493
+ queue.push({
1494
+ message,
1495
+ deviceId,
1496
+ createdAt: Date.now(),
1497
+ delivered: false
1498
+ });
1499
+ this.runtime.logger.debug(
1500
+ `Message queued for ${deviceId}: ${message.id}`
1501
+ );
1502
+ return true;
1503
+ }
1504
+ /**
1505
+ * Acknowledge messages
1506
+ */
1507
+ async acknowledgeMessages(messageIds) {
1508
+ for (const deviceId of this.messageQueue.keys()) {
1509
+ const queue = this.messageQueue.get(deviceId);
1510
+ if (!queue) continue;
1511
+ for (const msgId of messageIds) {
1512
+ const index = queue.findIndex((m) => m.message.id === msgId);
1513
+ if (index >= 0) {
1514
+ queue.splice(index, 1);
1515
+ }
1516
+ }
1517
+ if (queue.length === 0) {
1518
+ this.messageQueue.delete(deviceId);
1519
+ }
1520
+ }
1521
+ this.runtime.logger.debug(`Acknowledged ${messageIds.length} messages`);
1522
+ }
1523
+ /**
1524
+ * Check if message has been seen (deduplication)
1525
+ */
1526
+ isMessageSeen(messageId) {
1527
+ return this.seenMessages.has(messageId);
1528
+ }
1529
+ /**
1530
+ * Mark message as seen
1531
+ */
1532
+ markMessageSeen(messageId) {
1533
+ this.seenMessages.add(messageId);
1534
+ if (this.seenMessages.size > 1e4) {
1535
+ const entries = Array.from(this.seenMessages);
1536
+ for (let i = 0; i < entries.length / 2; i++) {
1537
+ this.seenMessages.delete(entries[i]);
1538
+ }
1539
+ }
1540
+ }
1541
+ /**
1542
+ * Get queue size for a device
1543
+ */
1544
+ getQueueSize(deviceId) {
1545
+ const queue = this.messageQueue.get(deviceId);
1546
+ return queue?.filter((m) => !m.delivered).length ?? 0;
1547
+ }
1548
+ /**
1549
+ * Get total queue size
1550
+ */
1551
+ getTotalQueueSize() {
1552
+ let total = 0;
1553
+ for (const queue of this.messageQueue.values()) {
1554
+ total += queue.filter((m) => !m.delivered).length;
1555
+ }
1556
+ return total;
1557
+ }
1558
+ };
1559
+
1560
+ // src/probe.ts
1561
+ var Probe = class {
1562
+ runtime;
1563
+ startTime = Date.now();
1564
+ lastError = null;
1565
+ lastErrorTime = null;
1566
+ errorCounts = /* @__PURE__ */ new Map();
1567
+ healthChecks = /* @__PURE__ */ new Map();
1568
+ constructor(runtime) {
1569
+ this.runtime = runtime;
1570
+ this.registerHealthCheck("uptime", this.checkUptime);
1571
+ this.registerHealthCheck("errors", this.checkErrors);
1572
+ }
1573
+ /**
1574
+ * Register a custom health check
1575
+ */
1576
+ registerHealthCheck(name, check) {
1577
+ this.healthChecks.set(name, check);
1578
+ }
1579
+ /**
1580
+ * Run all health checks
1581
+ */
1582
+ async runHealthChecks() {
1583
+ const checks = /* @__PURE__ */ new Map();
1584
+ let overallStatus = "healthy" /* HEALTHY */;
1585
+ for (const [name, checkFn] of this.healthChecks.entries()) {
1586
+ const start = Date.now();
1587
+ try {
1588
+ const result = await checkFn();
1589
+ const duration = Date.now() - start;
1590
+ checks.set(name, {
1591
+ passed: result.passed,
1592
+ message: result.message,
1593
+ duration
1594
+ });
1595
+ if (!result.passed && overallStatus === "healthy" /* HEALTHY */) {
1596
+ overallStatus = "degraded" /* DEGRADED */;
1597
+ }
1598
+ } catch (error) {
1599
+ const duration = Date.now() - start;
1600
+ checks.set(name, {
1601
+ passed: false,
1602
+ message: "Health check failed with exception",
1603
+ duration,
1604
+ error
1605
+ });
1606
+ overallStatus = "unhealthy" /* UNHEALTHY */;
1607
+ }
1608
+ }
1609
+ return {
1610
+ status: overallStatus,
1611
+ checks,
1612
+ timestamp: Date.now()
1613
+ };
1614
+ }
1615
+ /**
1616
+ * Get channel status for reporting
1617
+ */
1618
+ getChannelStatus(configured, running, activeAccounts = 0) {
1619
+ return {
1620
+ configured,
1621
+ running,
1622
+ lastError: this.lastError ?? void 0,
1623
+ activeAccounts,
1624
+ uptime: Date.now() - this.startTime,
1625
+ status: running ? "healthy" /* HEALTHY */ : "unhealthy" /* UNHEALTHY */
1626
+ };
1627
+ }
1628
+ /**
1629
+ * Get health status for API endpoint
1630
+ */
1631
+ async getHealthStatus(activeConnections = 0, queuedMessages = 0) {
1632
+ const healthResult = await this.runHealthChecks();
1633
+ let status;
1634
+ switch (healthResult.status) {
1635
+ case "healthy" /* HEALTHY */:
1636
+ status = "healthy";
1637
+ break;
1638
+ case "degraded" /* DEGRADED */:
1639
+ status = "degraded";
1640
+ break;
1641
+ case "unhealthy" /* UNHEALTHY */:
1642
+ status = "unhealthy";
1643
+ break;
1644
+ }
1645
+ return {
1646
+ status,
1647
+ uptime: Date.now() - this.startTime,
1648
+ activeConnections,
1649
+ queuedMessages,
1650
+ lastError: this.lastError ?? void 0
1651
+ };
1652
+ }
1653
+ /**
1654
+ * Record an error
1655
+ */
1656
+ recordError(error, category = "general") {
1657
+ this.lastError = error;
1658
+ this.lastErrorTime = Date.now();
1659
+ const count = this.errorCounts.get(category) ?? 0;
1660
+ this.errorCounts.set(category, count + 1);
1661
+ this.runtime.logger.error(`[${category}] ${error}`);
1662
+ }
1663
+ /**
1664
+ * Get last error
1665
+ */
1666
+ getLastError() {
1667
+ if (this.lastErrorTime && Date.now() - this.lastErrorTime < 3e5) {
1668
+ return this.lastError;
1669
+ }
1670
+ return null;
1671
+ }
1672
+ /**
1673
+ * Clear last error
1674
+ */
1675
+ clearLastError() {
1676
+ this.lastError = null;
1677
+ this.lastErrorTime = null;
1678
+ }
1679
+ /**
1680
+ * Get error counts by category
1681
+ */
1682
+ getErrorCounts() {
1683
+ return Object.fromEntries(this.errorCounts);
1684
+ }
1685
+ /**
1686
+ * Reset error counts
1687
+ */
1688
+ resetErrorCounts() {
1689
+ this.errorCounts.clear();
1690
+ }
1691
+ /**
1692
+ * Get uptime in milliseconds
1693
+ */
1694
+ getUptime() {
1695
+ return Date.now() - this.startTime;
1696
+ }
1697
+ /**
1698
+ * Get uptime formatted as human-readable string
1699
+ */
1700
+ getUptimeString() {
1701
+ const uptime = this.getUptime();
1702
+ const seconds = Math.floor(uptime / 1e3);
1703
+ const minutes = Math.floor(seconds / 60);
1704
+ const hours = Math.floor(minutes / 60);
1705
+ const days = Math.floor(hours / 24);
1706
+ if (days > 0) {
1707
+ return `${days}d ${hours % 24}h ${minutes % 60}m`;
1708
+ } else if (hours > 0) {
1709
+ return `${hours}h ${minutes % 60}m`;
1710
+ } else if (minutes > 0) {
1711
+ return `${minutes}m ${seconds % 60}s`;
1712
+ } else {
1713
+ return `${seconds}s`;
1714
+ }
1715
+ }
1716
+ /**
1717
+ * Health check: uptime
1718
+ */
1719
+ checkUptime = async () => {
1720
+ const uptime = this.getUptime();
1721
+ return {
1722
+ passed: uptime > 0,
1723
+ message: `Uptime: ${this.getUptimeString()}`,
1724
+ duration: 0
1725
+ };
1726
+ };
1727
+ /**
1728
+ * Health check: errors
1729
+ */
1730
+ checkErrors = async () => {
1731
+ const recentErrors = this.lastErrorTime && Date.now() - this.lastErrorTime < 6e4;
1732
+ return {
1733
+ passed: !recentErrors,
1734
+ message: recentErrors ? `Recent error: ${this.lastError}` : "No recent errors",
1735
+ duration: 0
1736
+ };
1737
+ };
1738
+ /**
1739
+ * Create status adapter for OpenClaw integration
1740
+ */
1741
+ createStatusAdapter(configured) {
1742
+ return {
1743
+ getStatus: async (running, activeAccounts = 0, activeConnections = 0, queuedMessages = 0) => ({
1744
+ ...this.getChannelStatus(configured, running, activeAccounts),
1745
+ activeConnections,
1746
+ queuedMessages
1747
+ })
1748
+ };
1749
+ }
1750
+ };
1751
+
1752
+ // src/metrics.ts
1753
+ var MetricsManager = class {
1754
+ metrics = /* @__PURE__ */ new Map();
1755
+ counters = /* @__PURE__ */ new Map();
1756
+ gauges = /* @__PURE__ */ new Map();
1757
+ histograms = /* @__PURE__ */ new Map();
1758
+ histogramBuckets = /* @__PURE__ */ new Map();
1759
+ constructor(_runtime) {
1760
+ this.initializeDefaultMetrics();
1761
+ }
1762
+ /**
1763
+ * 初始化默认指标
1764
+ */
1765
+ initializeDefaultMetrics() {
1766
+ this.declareMetric({
1767
+ name: "lingyao_connection_count",
1768
+ type: "gauge" /* GAUGE */,
1769
+ description: "\u5F53\u524D\u6D3B\u8DC3\u8FDE\u63A5\u6570"
1770
+ });
1771
+ this.declareMetric({
1772
+ name: "lingyao_connection_total",
1773
+ type: "counter" /* COUNTER */,
1774
+ description: "\u603B\u8FDE\u63A5\u6B21\u6570"
1775
+ });
1776
+ this.declareMetric({
1777
+ name: "lingyao_disconnection_total",
1778
+ type: "counter" /* COUNTER */,
1779
+ description: "\u603B\u65AD\u5F00\u8FDE\u63A5\u6B21\u6570"
1780
+ });
1781
+ this.declareMetric({
1782
+ name: "lingyao_message_total",
1783
+ type: "counter" /* COUNTER */,
1784
+ description: "\u603B\u6D88\u606F\u6570",
1785
+ labels: ["direction", "type"]
1786
+ });
1787
+ this.declareMetric({
1788
+ name: "lingyao_message_failed_total",
1789
+ type: "counter" /* COUNTER */,
1790
+ description: "\u5931\u8D25\u6D88\u606F\u603B\u6570",
1791
+ labels: ["direction", "reason"]
1792
+ });
1793
+ this.declareMetric({
1794
+ name: "lingyao_message_latency",
1795
+ type: "histogram" /* HISTOGRAM */,
1796
+ description: "\u6D88\u606F\u5904\u7406\u5EF6\u8FDF",
1797
+ buckets: [1, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3, 1e4]
1798
+ });
1799
+ this.declareMetric({
1800
+ name: "lingyao_error_total",
1801
+ type: "counter" /* COUNTER */,
1802
+ description: "\u9519\u8BEF\u603B\u6570",
1803
+ labels: ["type", "severity"]
1804
+ });
1805
+ this.declareMetric({
1806
+ name: "lingyao_heartbeat_total",
1807
+ type: "counter" /* COUNTER */,
1808
+ description: "\u5FC3\u8DF3\u603B\u6570"
1809
+ });
1810
+ this.declareMetric({
1811
+ name: "lingyao_heartbeat_missed_total",
1812
+ type: "counter" /* COUNTER */,
1813
+ description: "\u5FC3\u8DF3\u4E22\u5931\u603B\u6570"
1814
+ });
1815
+ this.declareMetric({
1816
+ name: "lingyao_reconnect_total",
1817
+ type: "counter" /* COUNTER */,
1818
+ description: "\u91CD\u8FDE\u6B21\u6570"
1819
+ });
1820
+ this.declareMetric({
1821
+ name: "lingyao_queue_size",
1822
+ type: "gauge" /* GAUGE */,
1823
+ description: "\u5F53\u524D\u961F\u5217\u5927\u5C0F"
1824
+ });
1825
+ }
1826
+ /**
1827
+ * 声明指标
1828
+ */
1829
+ declareMetric(config) {
1830
+ if (config.type === "histogram" /* HISTOGRAM */ && config.buckets) {
1831
+ this.histogramBuckets.set(config.name, config.buckets);
1832
+ }
1833
+ }
1834
+ /**
1835
+ * 计数器增加
1836
+ */
1837
+ incrementCounter(name, value = 1, labels) {
1838
+ const current = this.counters.get(name) || 0;
1839
+ this.counters.set(name, current + value);
1840
+ this.recordMetric({
1841
+ name,
1842
+ type: "counter" /* COUNTER */,
1843
+ value: current + value,
1844
+ timestamp: Date.now(),
1845
+ labels
1846
+ });
1847
+ }
1848
+ /**
1849
+ * 设置仪表盘值
1850
+ */
1851
+ setGauge(name, value, labels) {
1852
+ this.gauges.set(name, value);
1853
+ this.recordMetric({
1854
+ name,
1855
+ type: "gauge" /* GAUGE */,
1856
+ value,
1857
+ timestamp: Date.now(),
1858
+ labels
1859
+ });
1860
+ }
1861
+ /**
1862
+ * 记录直方图值
1863
+ */
1864
+ recordHistogram(name, value, labels) {
1865
+ let values = this.histograms.get(name);
1866
+ if (!values) {
1867
+ values = [];
1868
+ this.histograms.set(name, values);
1869
+ }
1870
+ values.push(value);
1871
+ this.recordMetric({
1872
+ name,
1873
+ type: "histogram" /* HISTOGRAM */,
1874
+ value,
1875
+ timestamp: Date.now(),
1876
+ labels
1877
+ });
1878
+ }
1879
+ /**
1880
+ * 记录指标
1881
+ */
1882
+ recordMetric(data) {
1883
+ this.metrics.set(`${data.name}_${Date.now()}`, data);
1884
+ if (this.metrics.size > 1e4) {
1885
+ const entries = Array.from(this.metrics.entries());
1886
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
1887
+ for (let i = 0; i < 1e3; i++) {
1888
+ this.metrics.delete(entries[i][0]);
1889
+ }
1890
+ }
1891
+ }
1892
+ /**
1893
+ * 获取所有指标
1894
+ */
1895
+ getMetrics() {
1896
+ return Array.from(this.metrics.values());
1897
+ }
1898
+ /**
1899
+ * 获取特定指标的当前值
1900
+ */
1901
+ getMetricValue(name) {
1902
+ if (this.counters.has(name)) {
1903
+ return this.counters.get(name);
1904
+ }
1905
+ if (this.gauges.has(name)) {
1906
+ return this.gauges.get(name);
1907
+ }
1908
+ return void 0;
1909
+ }
1910
+ /**
1911
+ * 获取直方图统计数据
1912
+ */
1913
+ getHistogramStats(name) {
1914
+ const values = this.histograms.get(name);
1915
+ if (!values || values.length === 0) {
1916
+ return null;
1917
+ }
1918
+ const sorted = [...values].sort((a, b) => a - b);
1919
+ const count = sorted.length;
1920
+ const sum = sorted.reduce((a, b) => a + b, 0);
1921
+ return {
1922
+ count,
1923
+ min: sorted[0],
1924
+ max: sorted[count - 1],
1925
+ avg: sum / count,
1926
+ p50: sorted[Math.floor(count * 0.5)],
1927
+ p95: sorted[Math.floor(count * 0.95)],
1928
+ p99: sorted[Math.floor(count * 0.99)]
1929
+ };
1930
+ }
1931
+ /**
1932
+ * 重置所有指标
1933
+ */
1934
+ reset() {
1935
+ this.metrics.clear();
1936
+ this.counters.clear();
1937
+ this.gauges.clear();
1938
+ this.histograms.clear();
1939
+ }
1940
+ /**
1941
+ * 生成 Prometheus 格式的指标导出
1942
+ */
1943
+ exportPrometheus() {
1944
+ const lines = [];
1945
+ for (const [name, value] of [...this.counters, ...this.gauges]) {
1946
+ lines.push(`${name} ${value}`);
1947
+ }
1948
+ for (const [name] of this.histograms) {
1949
+ const stats = this.getHistogramStats(name);
1950
+ if (stats) {
1951
+ lines.push(`${name}_count ${stats.count}`);
1952
+ lines.push(`${name}_sum ${stats.avg * stats.count}`);
1953
+ lines.push(`${name}_bucket{le="+Inf"} ${stats.count}`);
1954
+ }
1955
+ }
1956
+ return lines.join("\n");
1957
+ }
1958
+ };
1959
+ var Monitor = class {
1960
+ runtime;
1961
+ metrics;
1962
+ eventHandlers = /* @__PURE__ */ new Map();
1963
+ constructor(runtime) {
1964
+ this.runtime = runtime;
1965
+ this.metrics = new MetricsManager(runtime);
1966
+ }
1967
+ /**
1968
+ * 记录事件
1969
+ */
1970
+ recordEvent(event, data = {}) {
1971
+ const eventData = {
1972
+ event,
1973
+ timestamp: Date.now(),
1974
+ data
1975
+ };
1976
+ this.updateMetricsForEvent(event, data);
1977
+ const handlers = this.eventHandlers.get(event) || [];
1978
+ handlers.forEach((handler) => {
1979
+ try {
1980
+ handler(eventData);
1981
+ } catch (error) {
1982
+ this.runtime.logger.error("Error in monitoring event handler", error);
1983
+ }
1984
+ });
1985
+ this.runtime.logger.debug(`Monitoring event: ${event}`, data);
1986
+ }
1987
+ /**
1988
+ * 为事件更新指标
1989
+ */
1990
+ updateMetricsForEvent(event, data) {
1991
+ switch (event) {
1992
+ case "connection_open" /* CONNECTION_OPEN */:
1993
+ this.metrics.incrementCounter("lingyao_connection_total");
1994
+ this.metrics.setGauge("lingyao_connection_count", data.activeConnections || 1);
1995
+ break;
1996
+ case "connection_close" /* CONNECTION_CLOSE */:
1997
+ this.metrics.incrementCounter("lingyao_disconnection_total");
1998
+ this.metrics.setGauge("lingyao_connection_count", data.activeConnections || 0);
1999
+ break;
2000
+ case "reconnect" /* RECONNECT */:
2001
+ this.metrics.incrementCounter("lingyao_reconnect_total");
2002
+ break;
2003
+ case "message_received" /* MESSAGE_RECEIVED */:
2004
+ this.metrics.incrementCounter("lingyao_message_total", 1, {
2005
+ direction: "in",
2006
+ type: data.messageType || "unknown"
2007
+ });
2008
+ break;
2009
+ case "message_sent" /* MESSAGE_SENT */:
2010
+ this.metrics.incrementCounter("lingyao_message_total", 1, {
2011
+ direction: "out",
2012
+ type: data.messageType || "unknown"
2013
+ });
2014
+ break;
2015
+ case "message_delivered" /* MESSAGE_DELIVERED */:
2016
+ if (data.latency) {
2017
+ this.metrics.recordHistogram("lingyao_message_latency", data.latency);
2018
+ }
2019
+ break;
2020
+ case "message_failed" /* MESSAGE_FAILED */:
2021
+ this.metrics.incrementCounter("lingyao_message_failed_total", 1, {
2022
+ direction: data.direction || "unknown",
2023
+ reason: data.reason || "unknown"
2024
+ });
2025
+ break;
2026
+ case "heartbeat_sent" /* HEARTBEAT_SENT */:
2027
+ this.metrics.incrementCounter("lingyao_heartbeat_total");
2028
+ break;
2029
+ case "heartbeat_missed" /* HEARTBEAT_MISSED */:
2030
+ this.metrics.incrementCounter("lingyao_heartbeat_missed_total");
2031
+ break;
2032
+ case "error_occurred" /* ERROR_OCCURRED */:
2033
+ this.metrics.incrementCounter("lingyao_error_total", 1, {
2034
+ type: data.errorType || "unknown",
2035
+ severity: data.severity || "medium"
2036
+ });
2037
+ break;
2038
+ }
2039
+ }
2040
+ /**
2041
+ * 注册事件处理器
2042
+ */
2043
+ on(event, handler) {
2044
+ if (!this.eventHandlers.has(event)) {
2045
+ this.eventHandlers.set(event, []);
2046
+ }
2047
+ this.eventHandlers.get(event).push(handler);
2048
+ }
2049
+ /**
2050
+ * 移除事件处理器
2051
+ */
2052
+ off(event, handler) {
2053
+ const handlers = this.eventHandlers.get(event);
2054
+ if (handlers) {
2055
+ const index = handlers.indexOf(handler);
2056
+ if (index > -1) {
2057
+ handlers.splice(index, 1);
2058
+ }
2059
+ }
2060
+ }
2061
+ /**
2062
+ * 获取指标管理器
2063
+ */
2064
+ getMetrics() {
2065
+ return this.metrics;
2066
+ }
2067
+ /**
2068
+ * 获取摘要报告
2069
+ */
2070
+ getSummary() {
2071
+ return {
2072
+ uptime: Date.now() - this.runtime.startTime,
2073
+ connections: this.metrics.getMetricValue("lingyao_connection_total") || 0,
2074
+ messages: {
2075
+ sent: this.metrics.getMetricValue("lingyao_message_total") || 0,
2076
+ received: this.metrics.getMetricValue("lingyao_message_total") || 0,
2077
+ failed: this.metrics.getMetricValue("lingyao_message_failed_total") || 0
2078
+ },
2079
+ errors: this.metrics.getMetricValue("lingyao_error_total") || 0,
2080
+ heartbeats: {
2081
+ sent: this.metrics.getMetricValue("lingyao_heartbeat_total") || 0,
2082
+ missed: this.metrics.getMetricValue("lingyao_heartbeat_missed_total") || 0
2083
+ }
2084
+ };
2085
+ }
2086
+ };
2087
+
2088
+ // src/errors.ts
2089
+ var LingyaoError = class extends Error {
2090
+ code;
2091
+ severity;
2092
+ timestamp;
2093
+ context;
2094
+ cause;
2095
+ constructor(message, code, severity = "medium" /* MEDIUM */, context, cause) {
2096
+ super(message);
2097
+ this.name = this.constructor.name;
2098
+ this.code = code;
2099
+ this.severity = severity;
2100
+ this.timestamp = Date.now();
2101
+ this.context = context;
2102
+ this.cause = cause;
2103
+ Error.captureStackTrace(this, this.constructor);
2104
+ }
2105
+ /**
2106
+ * 转换为 JSON 可序列化格式
2107
+ */
2108
+ toJSON() {
2109
+ return {
2110
+ name: this.name,
2111
+ message: this.message,
2112
+ code: this.code,
2113
+ severity: this.severity,
2114
+ timestamp: this.timestamp,
2115
+ context: this.context,
2116
+ cause: this.cause?.message,
2117
+ stack: this.stack
2118
+ };
2119
+ }
2120
+ };
2121
+ var ConnectionError = class extends LingyaoError {
2122
+ constructor(message, context, cause) {
2123
+ super(message, "CONNECTION_ERROR", "high" /* HIGH */, context, cause);
2124
+ }
2125
+ };
2126
+ var RetryableError = class extends LingyaoError {
2127
+ retryable;
2128
+ retryAfter;
2129
+ maxRetries;
2130
+ constructor(message, context, cause) {
2131
+ super(message, "RETRYABLE_ERROR", "low" /* LOW */, context, cause);
2132
+ this.retryable = true;
2133
+ this.retryAfter = context?.retryAfter;
2134
+ this.maxRetries = context?.maxRetries;
2135
+ }
2136
+ };
2137
+ var ErrorHandler = class {
2138
+ runtime;
2139
+ errorCounts = /* @__PURE__ */ new Map();
2140
+ lastErrors = /* @__PURE__ */ new Map();
2141
+ errorThresholds = /* @__PURE__ */ new Map();
2142
+ constructor(runtime) {
2143
+ this.runtime = runtime;
2144
+ this.setErrorThreshold("CONNECTION_ERROR", { count: 5, window: 6e4 });
2145
+ this.setErrorThreshold("AUTHENTICATION_ERROR", { count: 3, window: 6e4 });
2146
+ this.setErrorThreshold("NETWORK_ERROR", { count: 10, window: 6e4 });
2147
+ }
2148
+ /**
2149
+ * 处理错误
2150
+ */
2151
+ handleError(error) {
2152
+ const lingyaoError = error instanceof LingyaoError ? error : new LingyaoError(error.message, "UNKNOWN_ERROR", "medium" /* MEDIUM */, {}, error);
2153
+ this.recordError(lingyaoError);
2154
+ switch (lingyaoError.severity) {
2155
+ case "low" /* LOW */:
2156
+ this.runtime.logger.debug(lingyaoError.message, lingyaoError.toJSON());
2157
+ break;
2158
+ case "medium" /* MEDIUM */:
2159
+ this.runtime.logger.warn(lingyaoError.message, lingyaoError.toJSON());
2160
+ break;
2161
+ case "high" /* HIGH */:
2162
+ case "critical" /* CRITICAL */:
2163
+ this.runtime.logger.error(lingyaoError.message, lingyaoError.toJSON());
2164
+ break;
2165
+ }
2166
+ this.checkThresholds(lingyaoError);
2167
+ }
2168
+ /**
2169
+ * 记录错误
2170
+ */
2171
+ recordError(error) {
2172
+ const key = error.code;
2173
+ const count = (this.errorCounts.get(key) || 0) + 1;
2174
+ this.errorCounts.set(key, count);
2175
+ this.lastErrors.set(key, {
2176
+ error,
2177
+ timestamp: Date.now()
2178
+ });
2179
+ setTimeout(() => {
2180
+ const lastError = this.lastErrors.get(key);
2181
+ if (lastError && lastError.timestamp === error.timestamp) {
2182
+ this.errorCounts.set(key, (this.errorCounts.get(key) || 0) - 1);
2183
+ }
2184
+ }, 36e5);
2185
+ }
2186
+ /**
2187
+ * 检查错误阈值
2188
+ */
2189
+ checkThresholds(error) {
2190
+ const threshold = this.errorThresholds.get(error.code);
2191
+ if (!threshold) {
2192
+ return;
2193
+ }
2194
+ const count = this.errorCounts.get(error.code) || 0;
2195
+ if (count >= threshold.count) {
2196
+ this.runtime.logger.error(
2197
+ `Error threshold exceeded for ${error.code}`,
2198
+ {
2199
+ code: error.code,
2200
+ count,
2201
+ threshold: threshold.count,
2202
+ window: threshold.window
2203
+ }
2204
+ );
2205
+ this.triggerAlert(error, count);
2206
+ }
2207
+ }
2208
+ /**
2209
+ * 触发警报
2210
+ */
2211
+ triggerAlert(error, count) {
2212
+ this.runtime.logger.error("ALERT", {
2213
+ type: "error_threshold_exceeded",
2214
+ error: error.toJSON(),
2215
+ count
2216
+ });
2217
+ }
2218
+ /**
2219
+ * 设置错误阈值
2220
+ */
2221
+ setErrorThreshold(code, threshold) {
2222
+ this.errorThresholds.set(code, threshold);
2223
+ }
2224
+ /**
2225
+ * 获取错误统计
2226
+ */
2227
+ getErrorStats() {
2228
+ const byCode = {};
2229
+ let total = 0;
2230
+ for (const [code, count] of this.errorCounts) {
2231
+ byCode[code] = count;
2232
+ total += count;
2233
+ }
2234
+ const recent = Array.from(this.lastErrors.entries()).map(([code, { error, timestamp }]) => ({ code, error, timestamp })).sort((a, b) => b.timestamp - a.timestamp).slice(0, 10);
2235
+ return { byCode, total, recent };
2236
+ }
2237
+ /**
2238
+ * 重置错误计数
2239
+ */
2240
+ resetErrorCounts(code) {
2241
+ if (code) {
2242
+ this.errorCounts.delete(code);
2243
+ this.lastErrors.delete(code);
2244
+ } else {
2245
+ this.errorCounts.clear();
2246
+ this.lastErrors.clear();
2247
+ }
2248
+ }
2249
+ /**
2250
+ * 包装异步函数以自动处理错误
2251
+ */
2252
+ async wrapAsync(fn, context) {
2253
+ try {
2254
+ return await fn();
2255
+ } catch (error) {
2256
+ const operation = context?.operation || "unknown";
2257
+ if (error instanceof LingyaoError) {
2258
+ this.handleError(error);
2259
+ } else if (context?.retryable) {
2260
+ const retryableError = new RetryableError(
2261
+ `Operation "${operation}" failed`,
2262
+ { operation },
2263
+ error
2264
+ );
2265
+ this.handleError(retryableError);
2266
+ } else {
2267
+ this.handleError(new LingyaoError(
2268
+ `Operation "${operation}" failed: ${error.message}`,
2269
+ "OPERATION_FAILED",
2270
+ "medium" /* MEDIUM */,
2271
+ { operation },
2272
+ error
2273
+ ));
2274
+ }
2275
+ if (context?.fallback !== void 0) {
2276
+ return context.fallback;
2277
+ }
2278
+ throw error;
2279
+ }
2280
+ }
2281
+ /**
2282
+ * 创建带有错误处理的重试逻辑
2283
+ */
2284
+ async retry(fn, options = {}) {
2285
+ const {
2286
+ maxRetries = 3,
2287
+ retryDelay = 1e3,
2288
+ backoff = true,
2289
+ onRetry
2290
+ } = options;
2291
+ let lastError = null;
2292
+ let delay = retryDelay;
2293
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2294
+ try {
2295
+ return await fn();
2296
+ } catch (error) {
2297
+ lastError = error;
2298
+ if (attempt === maxRetries) {
2299
+ break;
2300
+ }
2301
+ if (onRetry) {
2302
+ onRetry(lastError, attempt + 1);
2303
+ }
2304
+ await new Promise((resolve) => setTimeout(resolve, delay));
2305
+ if (backoff) {
2306
+ delay *= 2;
2307
+ }
2308
+ }
2309
+ }
2310
+ throw new RetryableError(
2311
+ `Operation failed after ${maxRetries + 1} attempts`,
2312
+ { maxRetries },
2313
+ lastError ?? new Error("Unknown retry failure")
2314
+ );
2315
+ }
2316
+ /**
2317
+ * 判断错误是否可重试
2318
+ */
2319
+ isRetryable(error) {
2320
+ if (error instanceof RetryableError) {
2321
+ return error.retryable;
2322
+ }
2323
+ if (error instanceof LingyaoError) {
2324
+ switch (error.code) {
2325
+ case "NETWORK_ERROR":
2326
+ case "TIMEOUT_ERROR":
2327
+ case "CONNECTION_ERROR":
2328
+ return true;
2329
+ default:
2330
+ return false;
2331
+ }
2332
+ }
2333
+ return false;
2334
+ }
2335
+ /**
2336
+ * 判断错误是否需要降级处理
2337
+ */
2338
+ shouldDegrade(error) {
2339
+ if (error instanceof LingyaoError) {
2340
+ return error.severity === "high" /* HIGH */ || error.severity === "critical" /* CRITICAL */;
2341
+ }
2342
+ return false;
2343
+ }
2344
+ /**
2345
+ * 创建降级响应
2346
+ */
2347
+ createDegradedResponse(fallback, error) {
2348
+ return {
2349
+ success: false,
2350
+ degraded: true,
2351
+ fallback,
2352
+ error
2353
+ };
2354
+ }
2355
+ };
2356
+
2357
+ // src/orchestrator.ts
2358
+ function generateGatewayId(accountId) {
2359
+ const host = hostname().split(".")[0].replace(/[^a-z0-9]/gi, "").toLowerCase();
2360
+ const suffix = Math.random().toString(36).substring(2, 8);
2361
+ return `gw_openclaw_${host}_${accountId}_${suffix}`;
2362
+ }
2363
+ var MultiAccountOrchestrator = class {
2364
+ runtime;
2365
+ accounts = /* @__PURE__ */ new Map();
2366
+ messageHandler = null;
2367
+ constructor(runtime) {
2368
+ this.runtime = runtime;
2369
+ }
2370
+ /**
2371
+ * Set the message handler for delivering messages to the Agent.
2372
+ * Propagates to all running accounts.
2373
+ */
2374
+ setMessageHandler(handler) {
2375
+ this.messageHandler = handler;
2376
+ for (const state of this.accounts.values()) {
2377
+ if (state.messageProcessor) {
2378
+ state.messageProcessor.setMessageHandler(handler);
2379
+ }
2380
+ }
2381
+ }
2382
+ /**
2383
+ * Get the current message handler (for gateway adapter injection).
2384
+ */
2385
+ getMessageHandler() {
2386
+ return this.messageHandler;
2387
+ }
2388
+ /**
2389
+ * Start an account: create components, register to server, connect WS.
2390
+ */
2391
+ async start(account) {
2392
+ const { id: accountId } = account;
2393
+ const existing = this.accounts.get(accountId);
2394
+ if (existing?.status === "running") {
2395
+ this.runtime.logger.warn(`Account "${accountId}" is already running`);
2396
+ return;
2397
+ }
2398
+ this.runtime.logger.info(`Starting account "${accountId}"`);
2399
+ const gatewayId = generateGatewayId(accountId);
2400
+ const storagePrefix = `lingyao:${accountId}`;
2401
+ const accountManager = new AccountManager(this.runtime);
2402
+ const messageProcessor = new MessageProcessor(this.runtime, accountManager);
2403
+ const probe = new Probe(this.runtime);
2404
+ const monitor = new Monitor(this.runtime);
2405
+ const errorHandler = new ErrorHandler(this.runtime);
2406
+ const httpClient = new ServerHttpClient(
2407
+ this.runtime,
2408
+ gatewayId,
2409
+ { baseURL: LINGYAO_SERVER_URL },
2410
+ storagePrefix
2411
+ );
2412
+ const state = {
2413
+ accountId,
2414
+ wsClient: null,
2415
+ httpClient,
2416
+ accountManager,
2417
+ messageProcessor,
2418
+ probe,
2419
+ monitor,
2420
+ errorHandler,
2421
+ status: "starting",
2422
+ startTime: Date.now(),
2423
+ gatewayId
2424
+ };
2425
+ this.accounts.set(accountId, state);
2426
+ try {
2427
+ await accountManager.initialize();
2428
+ await messageProcessor.initialize();
2429
+ if (this.messageHandler) {
2430
+ messageProcessor.setMessageHandler(this.messageHandler);
2431
+ }
2432
+ await this.registerToServer(state);
2433
+ const wsClient = new LingyaoWSClient(this.runtime, {
2434
+ url: `${LINGYAO_SERVER_URL}/v1/gateway/ws`,
2435
+ gatewayId,
2436
+ token: httpClient.getGatewayToken() ?? void 0,
2437
+ reconnectInterval: 5e3,
2438
+ heartbeatInterval: 3e4,
2439
+ messageHandler: this.createMessageHandler(state),
2440
+ eventHandler: this.createEventHandler(state)
2441
+ });
2442
+ await wsClient.connect();
2443
+ state.wsClient = wsClient;
2444
+ state.status = "running";
2445
+ this.runtime.logger.info(`Account "${accountId}" started successfully`);
2446
+ } catch (error) {
2447
+ state.status = "error";
2448
+ this.runtime.logger.error(`Failed to start account "${accountId}"`, error);
2449
+ throw error;
2450
+ }
2451
+ }
2452
+ /**
2453
+ * Stop an account: disconnect WS, stop heartbeat.
2454
+ */
2455
+ async stop(accountId) {
2456
+ const state = this.accounts.get(accountId);
2457
+ if (!state || state.status === "stopped") {
2458
+ return;
2459
+ }
2460
+ this.runtime.logger.info(`Stopping account "${accountId}"`);
2461
+ state.status = "stopping";
2462
+ if (state.wsClient) {
2463
+ state.wsClient.disconnect();
2464
+ state.wsClient = null;
2465
+ }
2466
+ if (state.httpClient) {
2467
+ state.httpClient.stopHeartbeat();
2468
+ }
2469
+ state.status = "stopped";
2470
+ this.runtime.logger.info(`Account "${accountId}" stopped`);
2471
+ }
2472
+ /**
2473
+ * Stop all running accounts.
2474
+ */
2475
+ async stopAll() {
2476
+ const stops = Array.from(this.accounts.keys()).map((id) => this.stop(id));
2477
+ await Promise.all(stops);
2478
+ }
2479
+ /**
2480
+ * Get account state by ID.
2481
+ */
2482
+ getAccountState(accountId) {
2483
+ return this.accounts.get(accountId);
2484
+ }
2485
+ /**
2486
+ * Get account's WS client.
2487
+ */
2488
+ getWSClient(accountId) {
2489
+ return this.accounts.get(accountId)?.wsClient ?? null;
2490
+ }
2491
+ /**
2492
+ * Get account's HTTP client.
2493
+ */
2494
+ getHttpClient(accountId) {
2495
+ return this.accounts.get(accountId)?.httpClient ?? null;
2496
+ }
2497
+ /**
2498
+ * Get account's AccountManager.
2499
+ */
2500
+ getAccountManager(accountId) {
2501
+ return this.accounts.get(accountId)?.accountManager ?? null;
2502
+ }
2503
+ /**
2504
+ * Get account's Probe.
2505
+ */
2506
+ getProbe(accountId) {
2507
+ return this.accounts.get(accountId)?.probe ?? null;
2508
+ }
2509
+ /**
2510
+ * Get account's Monitor.
2511
+ */
2512
+ getMonitor(accountId) {
2513
+ return this.accounts.get(accountId)?.monitor ?? null;
2514
+ }
2515
+ /**
2516
+ * Send notification to a device on a specific account.
2517
+ * Returns true if sent, false if WS not connected.
2518
+ */
2519
+ sendNotification(accountId, deviceId, notification) {
2520
+ const wsClient = this.getWSClient(accountId);
2521
+ if (!wsClient || !wsClient.isConnected()) {
2522
+ return false;
2523
+ }
2524
+ wsClient.sendNotification(deviceId, notification);
2525
+ return true;
2526
+ }
2527
+ /**
2528
+ * List all running account IDs.
2529
+ */
2530
+ getRunningAccountIds() {
2531
+ return Array.from(this.accounts.entries()).filter(([, state]) => state.status === "running").map(([id]) => id);
2532
+ }
2533
+ /**
2534
+ * Register to lingyao server for a specific account.
2535
+ */
2536
+ async registerToServer(state) {
2537
+ if (!state.httpClient) {
2538
+ throw new Error("HTTP client not available");
2539
+ }
2540
+ try {
2541
+ const restored = await state.httpClient.restoreFromStorage();
2542
+ if (restored) {
2543
+ this.runtime.logger.info(`Account "${state.accountId}": session restored from storage`);
2544
+ return;
2545
+ }
2546
+ this.runtime.logger.info(`Account "${state.accountId}": registering to server...`, {
2547
+ gatewayId: state.gatewayId
2548
+ });
2549
+ const response = await state.httpClient.register({
2550
+ websocket: true,
2551
+ compression: false,
2552
+ maxMessageSize: 1048576
2553
+ });
2554
+ this.runtime.logger.info(`Account "${state.accountId}": registered successfully`, {
2555
+ expiresAt: new Date(response.expiresAt).toISOString()
2556
+ });
2557
+ } catch (error) {
2558
+ this.runtime.logger.error(`Account "${state.accountId}": registration failed`, error);
2559
+ }
2560
+ }
2561
+ /**
2562
+ * Create message handler for inbound App messages on a specific account.
2563
+ */
2564
+ createMessageHandler(state) {
2565
+ return async (message) => {
2566
+ try {
2567
+ const appMessage = message.payload;
2568
+ const deviceId = appMessage.deviceId;
2569
+ const msg = appMessage.message;
2570
+ this.runtime.logger.info(`[${state.accountId}] Received message from App`, {
2571
+ deviceId,
2572
+ messageType: msg.type,
2573
+ messageId: msg.id
2574
+ });
2575
+ state.monitor.recordEvent("message_received" /* MESSAGE_RECEIVED */, {
2576
+ deviceId,
2577
+ messageType: msg.type
2578
+ });
2579
+ switch (msg.type) {
2580
+ case "sync_diary":
2581
+ case "sync_memory":
2582
+ await this.handleSyncMessage(state, deviceId, msg);
2583
+ break;
2584
+ case "heartbeat":
2585
+ await state.accountManager.updateLastSeen(deviceId);
2586
+ state.monitor.recordEvent("heartbeat_received" /* HEARTBEAT_RECEIVED */, { deviceId });
2587
+ break;
2588
+ default:
2589
+ this.runtime.logger.warn(`[${state.accountId}] Unknown message type`, { type: msg.type });
2590
+ }
2591
+ } catch (error) {
2592
+ this.runtime.logger.error(`[${state.accountId}] Error handling App message`, error);
2593
+ state.monitor.recordEvent("error_occurred" /* ERROR_OCCURRED */, {
2594
+ errorType: "message_handling"
2595
+ });
2596
+ }
2597
+ };
2598
+ }
2599
+ /**
2600
+ * Handle sync message (diary or memory).
2601
+ */
2602
+ async handleSyncMessage(state, deviceId, message) {
2603
+ if (!state.messageProcessor) {
2604
+ this.runtime.logger.warn(`[${state.accountId}] Message processor not initialized`);
2605
+ return;
2606
+ }
2607
+ const agentMessage = {
2608
+ id: message.id,
2609
+ type: message.type === "sync_diary" ? "diary" : "memory",
2610
+ from: deviceId,
2611
+ deviceId,
2612
+ content: message.content,
2613
+ metadata: message.metadata || {},
2614
+ timestamp: message.timestamp
2615
+ };
2616
+ await state.messageProcessor.deliverToAgent(agentMessage);
2617
+ }
2618
+ /**
2619
+ * Create event handler for WS connection events on a specific account.
2620
+ */
2621
+ createEventHandler(state) {
2622
+ return (event) => {
2623
+ switch (event.type) {
2624
+ case "connected":
2625
+ this.runtime.logger.info(`[${state.accountId}] WS connected`, {
2626
+ connectionId: event.connectionId
2627
+ });
2628
+ state.monitor.recordEvent("connection_open" /* CONNECTION_OPEN */, {
2629
+ connectionId: event.connectionId
2630
+ });
2631
+ break;
2632
+ case "disconnected":
2633
+ this.runtime.logger.warn(`[${state.accountId}] WS disconnected`, {
2634
+ code: event.code,
2635
+ reason: event.reason
2636
+ });
2637
+ state.monitor.recordEvent("connection_close" /* CONNECTION_CLOSE */, {
2638
+ code: event.code,
2639
+ reason: event.reason
2640
+ });
2641
+ break;
2642
+ case "error":
2643
+ this.runtime.logger.error(`[${state.accountId}] WS error`, event.error);
2644
+ state.probe.recordError(event.error.message, "websocket");
2645
+ state.monitor.recordEvent("connection_error" /* CONNECTION_ERROR */, {
2646
+ error: event.error
2647
+ });
2648
+ state.errorHandler.handleError(event.error);
2649
+ break;
2650
+ }
2651
+ };
2652
+ }
2653
+ };
2654
+
2655
+ // src/channel.ts
2656
+ import { hostname as hostname2 } from "os";
2657
+
2658
+ // src/token.ts
2659
+ import { randomBytes as randomBytes2, createHash as createHash2, timingSafeEqual as timingSafeEqual2 } from "crypto";
2660
+ var TokenManager = class {
2661
+ constructor(_runtime) {
2662
+ void _runtime;
2663
+ }
2664
+ /**
2665
+ * Generate a new device token
2666
+ */
2667
+ generateDeviceToken(pairingId, deviceInfo, expiryDays = 30) {
2668
+ const deviceId = this.generateDeviceId();
2669
+ const secret = this.generateSecret();
2670
+ const token = this.generateTokenString(deviceId, secret);
2671
+ const expiresAt = Date.now() + expiryDays * 24 * 60 * 60 * 1e3;
2672
+ return {
2673
+ deviceId,
2674
+ pairingId,
2675
+ token,
2676
+ secret,
2677
+ expiresAt,
2678
+ deviceInfo
2679
+ };
2680
+ }
2681
+ /**
2682
+ * Validate a device token
2683
+ */
2684
+ validateToken(token, storedSecret) {
2685
+ try {
2686
+ const parts = token.split(".");
2687
+ if (parts.length !== 3) {
2688
+ return false;
2689
+ }
2690
+ const [headerB64, payloadB64, signatureB64] = parts;
2691
+ const expectedSignature = this.sign(`${headerB64}.${payloadB64}`, storedSecret);
2692
+ const providedSignature = Buffer.from(signatureB64, "base64url").toString("utf-8");
2693
+ return timingSafeEqual2(
2694
+ Buffer.from(expectedSignature, "utf-8"),
2695
+ Buffer.from(providedSignature, "utf-8")
2696
+ );
2697
+ } catch {
2698
+ return false;
2699
+ }
2700
+ }
2701
+ /**
2702
+ * Check if token is expired
2703
+ */
2704
+ isTokenExpired(token) {
2705
+ return token.expiresAt < Date.now();
2706
+ }
2707
+ /**
2708
+ * Refresh an existing token
2709
+ */
2710
+ refreshToken(oldToken, expiryDays = 30) {
2711
+ const newSecret = this.generateSecret();
2712
+ const newTokenString = this.generateTokenString(
2713
+ oldToken.deviceId,
2714
+ newSecret
2715
+ );
2716
+ const expiresAt = Date.now() + expiryDays * 24 * 60 * 60 * 1e3;
2717
+ return {
2718
+ ...oldToken,
2719
+ token: newTokenString,
2720
+ secret: newSecret,
2721
+ expiresAt
2722
+ };
2723
+ }
2724
+ /**
2725
+ * Extract device ID from token string
2726
+ */
2727
+ extractDeviceId(token) {
2728
+ try {
2729
+ const parts = token.split(".");
2730
+ if (parts.length !== 3) {
2731
+ return null;
2732
+ }
2733
+ const payload = JSON.parse(
2734
+ Buffer.from(parts[1], "base64url").toString("utf-8")
2735
+ );
2736
+ return payload.did || null;
2737
+ } catch {
2738
+ return null;
2739
+ }
2740
+ }
2741
+ /**
2742
+ * Generate a unique device ID
2743
+ */
2744
+ generateDeviceId() {
2745
+ return `lingyao_${randomBytes2(16).toString("hex")}`;
2746
+ }
2747
+ /**
2748
+ * Generate a random secret key
2749
+ */
2750
+ generateSecret() {
2751
+ return randomBytes2(32).toString("hex");
2752
+ }
2753
+ /**
2754
+ * Generate a JWT-like token string
2755
+ */
2756
+ generateTokenString(deviceId, secret) {
2757
+ const header = {
2758
+ alg: "HS256",
2759
+ typ: "LingyaoToken"
2760
+ };
2761
+ const payload = {
2762
+ did: deviceId,
2763
+ iat: Date.now()
2764
+ };
2765
+ const headerB64 = this.base64UrlEncode(JSON.stringify(header));
2766
+ const payloadB64 = this.base64UrlEncode(JSON.stringify(payload));
2767
+ const signature = this.sign(`${headerB64}.${payloadB64}`, secret);
2768
+ const signatureB64 = this.base64UrlEncode(signature);
2769
+ return `${headerB64}.${payloadB64}.${signatureB64}`;
2770
+ }
2771
+ /**
2772
+ * Sign data with secret
2773
+ */
2774
+ sign(data, secret) {
2775
+ return createHash2("sha256").update(data + secret).digest("base64");
2776
+ }
2777
+ /**
2778
+ * Base64URL encode without padding
2779
+ */
2780
+ base64UrlEncode(data) {
2781
+ return Buffer.from(data).toString("base64url").replace(/=/g, "");
2782
+ }
2783
+ };
2784
+
2785
+ // src/channel.ts
2786
+ var LingyaoChannel = class {
2787
+ runtime;
2788
+ config;
2789
+ accountManager;
2790
+ tokenManager;
2791
+ serverClient = null;
2792
+ wsClient = null;
2793
+ processor = null;
2794
+ probe;
2795
+ monitor;
2796
+ errorHandler;
2797
+ startTime = 0;
2798
+ gatewayId;
2799
+ isRunning = false;
2800
+ messageHandler = null;
2801
+ constructor(runtime, config) {
2802
+ this.runtime = runtime;
2803
+ this.config = config;
2804
+ this.startTime = Date.now();
2805
+ this.gatewayId = this.generateGatewayId();
2806
+ setRuntime(runtime);
2807
+ this.accountManager = new AccountManager(runtime);
2808
+ this.tokenManager = new TokenManager(runtime);
2809
+ this.probe = new Probe(runtime);
2810
+ this.monitor = new Monitor(runtime);
2811
+ this.errorHandler = new ErrorHandler(runtime);
2812
+ this.serverClient = new ServerHttpClient(runtime, this.gatewayId, {
2813
+ baseURL: LINGYAO_SERVER_URL,
2814
+ apiBase: "/v1"
2815
+ });
2816
+ }
2817
+ /**
2818
+ * 初始化频道
2819
+ */
2820
+ async initialize() {
2821
+ this.runtime.logger.info("Initializing Lingyao Channel (Server Relay Mode)...");
2822
+ await this.accountManager.initialize();
2823
+ this.processor = new MessageProcessor(
2824
+ this.runtime,
2825
+ this.accountManager
2826
+ );
2827
+ await this.processor.initialize();
2828
+ if (this.messageHandler) {
2829
+ this.processor.setMessageHandler(this.messageHandler);
2830
+ }
2831
+ this.runtime.logger.info("Lingyao Channel initialized successfully");
2832
+ }
2833
+ /**
2834
+ * 启动频道
2835
+ */
2836
+ async start() {
2837
+ if (this.isRunning) {
2838
+ this.runtime.logger.warn("Lingyao Channel is already running");
2839
+ return;
2840
+ }
2841
+ this.runtime.logger.info("Starting Lingyao Channel...");
2842
+ try {
2843
+ await this.registerToServer();
2844
+ this.wsClient = new LingyaoWSClient(this.runtime, {
2845
+ url: `${LINGYAO_SERVER_URL}/v1/gateway/ws`,
2846
+ gatewayId: this.gatewayId,
2847
+ token: this.serverClient?.getGatewayToken() ?? void 0,
2848
+ reconnectInterval: 5e3,
2849
+ heartbeatInterval: 3e4,
2850
+ messageHandler: this.handleAppMessage.bind(this),
2851
+ eventHandler: this.handleClientEvent.bind(this)
2852
+ });
2853
+ await this.wsClient.connect();
2854
+ this.isRunning = true;
2855
+ this.runtime.logger.info("Lingyao Channel started successfully");
2856
+ } catch (error) {
2857
+ this.runtime.logger.error("Failed to start Lingyao Channel", error);
2858
+ throw error;
2859
+ }
2860
+ }
2861
+ /**
2862
+ * 向 lingyao 服务器注册 Gateway
2863
+ */
2864
+ async registerToServer() {
2865
+ if (!this.serverClient) {
2866
+ throw new Error("Server client not available");
2867
+ }
2868
+ try {
2869
+ const restored = await this.serverClient.restoreFromStorage();
2870
+ if (restored) {
2871
+ this.runtime.logger.info("Previous server registration restored");
2872
+ return;
2873
+ }
2874
+ this.runtime.logger.info("Registering to lingyao server...", {
2875
+ gatewayId: this.gatewayId
2876
+ });
2877
+ const registerResponse = await this.serverClient.register({
2878
+ websocket: true,
2879
+ compression: false,
2880
+ maxMessageSize: 1048576
2881
+ });
2882
+ this.runtime.logger.info("Registered to lingyao server successfully", {
2883
+ expiresAt: new Date(registerResponse.expiresAt).toISOString(),
2884
+ heartbeatInterval: registerResponse.serverConfig.heartbeatInterval
2885
+ });
2886
+ } catch (error) {
2887
+ const err = error;
2888
+ this.runtime.logger.error("Failed to register to server", err);
2889
+ this.runtime.logger.info("Will attempt WebSocket connection anyway");
2890
+ }
2891
+ }
2892
+ /**
2893
+ * 处理来自鸿蒙 App 的消息
2894
+ */
2895
+ async handleAppMessage(message) {
2896
+ try {
2897
+ const appMessage = message.payload;
2898
+ const deviceId = appMessage.deviceId;
2899
+ const msg = appMessage.message;
2900
+ this.runtime.logger.info("Received message from App", {
2901
+ deviceId,
2902
+ messageType: msg.type,
2903
+ messageId: msg.id
2904
+ });
2905
+ this.monitor.recordEvent("message_received" /* MESSAGE_RECEIVED */, {
2906
+ deviceId,
2907
+ messageType: msg.type,
2908
+ messageId: msg.id
2909
+ });
2910
+ switch (msg.type) {
2911
+ case "sync_diary":
2912
+ case "sync_memory":
2913
+ await this.handleSyncMessage(deviceId, msg);
2914
+ break;
2915
+ case "heartbeat":
2916
+ this.monitor.recordEvent("heartbeat_received" /* HEARTBEAT_RECEIVED */, {
2917
+ deviceId
2918
+ });
2919
+ break;
2920
+ default:
2921
+ this.runtime.logger.warn("Unknown message type", { type: msg.type });
2922
+ }
2923
+ } catch (error) {
2924
+ this.runtime.logger.error("Error handling App message", error);
2925
+ this.monitor.recordEvent("error_occurred" /* ERROR_OCCURRED */, {
2926
+ errorType: "message_handling",
2927
+ severity: "medium"
2928
+ });
2929
+ }
2930
+ }
2931
+ /**
2932
+ * 处理同步消息(日记、记忆等)
2933
+ */
2934
+ async handleSyncMessage(deviceId, message) {
2935
+ if (!this.processor) {
2936
+ this.runtime.logger.warn("Message processor not initialized");
2937
+ return;
2938
+ }
2939
+ const agentMessage = {
2940
+ id: message.id,
2941
+ type: this.mapMessageType(message.type),
2942
+ from: deviceId,
2943
+ deviceId,
2944
+ content: message.content,
2945
+ metadata: message.metadata || {},
2946
+ timestamp: message.timestamp
2947
+ };
2948
+ await this.processor.deliverToAgent(agentMessage);
2949
+ }
2950
+ /**
2951
+ * 映射消息类型
2952
+ */
2953
+ mapMessageType(appType) {
2954
+ return appType === "sync_diary" ? "diary" : "memory";
2955
+ }
2956
+ /**
2957
+ * 处理 WebSocket 客户端事件
2958
+ */
2959
+ handleClientEvent(event) {
2960
+ this.runtime.logger.debug("WebSocket client event", { type: event.type });
2961
+ switch (event.type) {
2962
+ case "connected":
2963
+ this.runtime.logger.info("Connected to Lingyao server", {
2964
+ connectionId: event.connectionId
2965
+ });
2966
+ this.monitor.recordEvent("connection_open" /* CONNECTION_OPEN */, {
2967
+ connectionId: event.connectionId
2968
+ });
2969
+ break;
2970
+ case "disconnected":
2971
+ this.runtime.logger.warn("Disconnected from Lingyao server", {
2972
+ code: event.code,
2973
+ reason: event.reason
2974
+ });
2975
+ this.monitor.recordEvent("connection_close" /* CONNECTION_CLOSE */, {
2976
+ code: event.code,
2977
+ reason: event.reason
2978
+ });
2979
+ break;
2980
+ case "error":
2981
+ this.runtime.logger.error("WebSocket client error", event.error);
2982
+ this.probe.recordError(event.error.message, "websocket");
2983
+ this.monitor.recordEvent("connection_error" /* CONNECTION_ERROR */, {
2984
+ error: event.error
2985
+ });
2986
+ this.errorHandler.handleError(
2987
+ new ConnectionError(
2988
+ `WebSocket error: ${event.error.message}`,
2989
+ { event: event.type },
2990
+ event.error
2991
+ )
2992
+ );
2993
+ break;
2994
+ }
2995
+ }
2996
+ /**
2997
+ * 停止频道
2998
+ */
2999
+ async stop() {
3000
+ if (!this.isRunning) {
3001
+ return;
3002
+ }
3003
+ this.runtime.logger.info("Stopping Lingyao Channel...");
3004
+ this.isRunning = false;
3005
+ if (this.wsClient) {
3006
+ this.wsClient.disconnect();
3007
+ this.wsClient = null;
3008
+ }
3009
+ if (this.serverClient) {
3010
+ this.serverClient.stopHeartbeat();
3011
+ }
3012
+ this.runtime.logger.info("Lingyao Channel stopped");
3013
+ }
3014
+ /**
3015
+ * 发送通知到设备
3016
+ */
3017
+ async sendNotification(deviceId, notification) {
3018
+ if (!this.wsClient || !this.wsClient.isConnected()) {
3019
+ this.monitor.recordEvent("message_failed" /* MESSAGE_FAILED */, {
3020
+ deviceId,
3021
+ reason: "not_connected",
3022
+ direction: "out"
3023
+ });
3024
+ return {
3025
+ success: false,
3026
+ error: "Not connected to Lingyao server"
3027
+ };
3028
+ }
3029
+ try {
3030
+ this.wsClient.sendNotification(deviceId, notification);
3031
+ this.monitor.recordEvent("message_sent" /* MESSAGE_SENT */, {
3032
+ deviceId,
3033
+ messageType: "notification"
3034
+ });
3035
+ return { success: true };
3036
+ } catch (error) {
3037
+ const err = error;
3038
+ this.monitor.recordEvent("message_failed" /* MESSAGE_FAILED */, {
3039
+ deviceId,
3040
+ reason: err.message,
3041
+ direction: "out"
3042
+ });
3043
+ return {
3044
+ success: false,
3045
+ error: err.message
3046
+ };
3047
+ }
3048
+ }
3049
+ /**
3050
+ * 发送文本到设备
3051
+ */
3052
+ async sendText(deviceId, text) {
3053
+ return await this.sendNotification(deviceId, {
3054
+ title: "OpenClaw",
3055
+ body: text
3056
+ });
3057
+ }
3058
+ /**
3059
+ * 获取健康状态
3060
+ */
3061
+ async getHealthStatus() {
3062
+ const activeConnections = this.wsClient?.isConnected() ? 1 : 0;
3063
+ const queuedMessages = this.processor?.getTotalQueueSize() ?? 0;
3064
+ const lastError = this.probe.getLastError() ?? void 0;
3065
+ let status = "healthy";
3066
+ if (lastError) {
3067
+ status = "degraded";
3068
+ }
3069
+ if (!this.config.enabled) {
3070
+ status = "unhealthy";
3071
+ }
3072
+ if (!this.wsClient?.isConnected()) {
3073
+ status = "degraded";
3074
+ }
3075
+ const uptime = Date.now() - this.startTime;
3076
+ return {
3077
+ status,
3078
+ uptime,
3079
+ activeConnections,
3080
+ queuedMessages,
3081
+ lastError
3082
+ };
3083
+ }
3084
+ /**
3085
+ * 获取配置
3086
+ */
3087
+ getConfig() {
3088
+ return { ...this.config };
3089
+ }
3090
+ /**
3091
+ * 更新配置
3092
+ */
3093
+ async updateConfig(updates) {
3094
+ this.config = { ...this.config, ...updates };
3095
+ this.runtime.logger.info("Configuration updated", updates);
3096
+ }
3097
+ /**
3098
+ * 获取账号管理器
3099
+ */
3100
+ getAccountManager() {
3101
+ return this.accountManager;
3102
+ }
3103
+ /**
3104
+ * 获取 Token 管理器
3105
+ */
3106
+ getTokenManager() {
3107
+ return this.tokenManager;
3108
+ }
3109
+ /**
3110
+ * 获取服务器客户端
3111
+ */
3112
+ getServerClient() {
3113
+ return this.serverClient;
3114
+ }
3115
+ /**
3116
+ /**
3117
+ * 获取 WebSocket 客户端
3118
+ */
3119
+ getWSClient() {
3120
+ return this.wsClient;
3121
+ }
3122
+ /**
3123
+ * 获取消息处理器
3124
+ */
3125
+ getMessageProcessor() {
3126
+ return this.processor;
3127
+ }
3128
+ /**
3129
+ * 设置消息处理器
3130
+ */
3131
+ setMessageHandler(handler) {
3132
+ this.messageHandler = handler;
3133
+ if (this.processor) {
3134
+ this.processor.setMessageHandler(handler);
3135
+ }
3136
+ this.runtime.logger.info("Message handler registered for Agent integration");
3137
+ }
3138
+ /**
3139
+ * 获取监控器
3140
+ */
3141
+ getMonitor() {
3142
+ return this.monitor;
3143
+ }
3144
+ /**
3145
+ * 获取错误处理器
3146
+ */
3147
+ getErrorHandler() {
3148
+ return this.errorHandler;
3149
+ }
3150
+ /**
3151
+ * 获取监控摘要
3152
+ */
3153
+ getMonitoringSummary() {
3154
+ return this.monitor.getSummary();
3155
+ }
3156
+ /**
3157
+ * 获取错误统计
3158
+ */
3159
+ getErrorStats() {
3160
+ return this.errorHandler.getErrorStats();
3161
+ }
3162
+ /**
3163
+ * 生成 Gateway ID
3164
+ */
3165
+ generateGatewayId() {
3166
+ const hostname3 = this.getRuntimeHostname();
3167
+ const randomSuffix = Math.random().toString(36).substring(2, 8);
3168
+ return `gw_openclaw_${hostname3}_${randomSuffix}`;
3169
+ }
3170
+ /**
3171
+ * 获取运行时主机名
3172
+ */
3173
+ getRuntimeHostname() {
3174
+ try {
3175
+ return hostname2().split(".")[0].replace(/[^a-z0-9]/gi, "").toLowerCase();
3176
+ } catch {
3177
+ return "unknown";
3178
+ }
3179
+ }
3180
+ };
3181
+
3182
+ // src/config-schema.ts
3183
+ import { z } from "zod";
3184
+ var lingyaoConfigSchema = z.object({
3185
+ enabled: z.boolean().default(true),
3186
+ maxOfflineMessages: z.number().int().min(1).max(1e3).optional().default(100),
3187
+ tokenExpiryDays: z.number().int().min(1).max(365).optional().default(30)
3188
+ });
3189
+ function validateConfig(config) {
3190
+ return lingyaoConfigSchema.parse(config);
3191
+ }
3192
+ function getDefaultConfig() {
3193
+ return {
3194
+ enabled: true,
3195
+ maxOfflineMessages: 100,
3196
+ tokenExpiryDays: 30
3197
+ };
3198
+ }
3199
+
3200
+ // src/index.ts
3201
+ var orchestrator = null;
3202
+ function getOrchestrator() {
3203
+ return orchestrator;
3204
+ }
3205
+ var configAdapter = createConfigAdapter();
3206
+ var messagingAdapter = createMessagingAdapter();
3207
+ var gatewayAdapter = createGatewayAdapter(getOrchestrator);
3208
+ var directoryAdapter = createDirectoryAdapter(getOrchestrator);
3209
+ var outboundAdapter = createOutboundAdapter(getOrchestrator);
3210
+ var statusAdapter = null;
3211
+ function getStatusAdapter(runtime) {
3212
+ if (!statusAdapter) {
3213
+ statusAdapter = createStatusAdapter(getOrchestrator, runtime);
3214
+ }
3215
+ return statusAdapter;
3216
+ }
3217
+ var securityOptions = {
3218
+ dm: {
3219
+ channelKey: "lingyao",
3220
+ resolvePolicy: (account) => account.dmPolicy,
3221
+ resolveAllowFrom: (account) => account.allowFrom,
3222
+ defaultPolicy: "paired"
3223
+ }
3224
+ };
3225
+ var pairingOptions = {
3226
+ text: {
3227
+ idLabel: "\u8BBE\u5907 ID",
3228
+ message: "\u8BBE\u5907\u5DF2\u6279\u51C6\u914D\u5BF9",
3229
+ notify: async (params) => {
3230
+ const orc = getOrchestrator();
3231
+ if (!orc) return;
3232
+ const config = params.cfg;
3233
+ const channels = config.channels;
3234
+ const lingyao = channels?.lingyao;
3235
+ const accountIds = lingyao?.accounts ? Object.keys(lingyao.accounts) : ["default"];
3236
+ for (const accountId of accountIds) {
3237
+ const sent = orc.sendNotification(accountId, params.id, {
3238
+ type: "pairing_confirmed",
3239
+ message: pairingOptions.text.message
3240
+ });
3241
+ if (sent) break;
3242
+ }
3243
+ }
3244
+ }
3245
+ };
3246
+ function buildPluginBase(runtime) {
3247
+ return {
3248
+ id: "lingyao",
3249
+ meta: {
3250
+ id: "lingyao",
3251
+ label: "\u7075\u723B",
3252
+ selectionLabel: "\u7075\u723B (HarmonyOS)",
3253
+ docsPath: "/channels/lingyao",
3254
+ docsLabel: "\u7075\u723B\u6587\u6863",
3255
+ blurb: "\u901A\u8FC7 lingyao.live \u670D\u52A1\u5668\u4E2D\u8F6C\u4E0E\u9E3F\u8499\u7075\u723B App \u53CC\u5411\u540C\u6B65\u65E5\u8BB0\u548C\u8BB0\u5FC6",
3256
+ order: 50,
3257
+ aliases: ["lingyao", "\u7075\u723B"]
3258
+ },
3259
+ capabilities: {
3260
+ chatTypes: ["direct"],
3261
+ media: false,
3262
+ reactions: false,
3263
+ threads: false,
3264
+ polls: false,
3265
+ edit: false,
3266
+ unsend: false,
3267
+ reply: false,
3268
+ effects: false,
3269
+ groupManagement: false,
3270
+ nativeCommands: false,
3271
+ blockStreaming: true
3272
+ },
3273
+ config: configAdapter,
3274
+ gateway: gatewayAdapter,
3275
+ outbound: outboundAdapter,
3276
+ status: getStatusAdapter(runtime),
3277
+ directory: directoryAdapter,
3278
+ messaging: messagingAdapter
3279
+ };
3280
+ }
3281
+ async function createPlugin(runtime, config = {}) {
3282
+ const fullConfig = { ...getDefaultConfig(), ...config };
3283
+ const validatedConfig = validateConfig(fullConfig);
3284
+ const channel = new LingyaoChannel(runtime, validatedConfig);
3285
+ await channel.initialize();
3286
+ return channel;
3287
+ }
3288
+ var pluginMetadata = {
3289
+ name: "lingyao",
3290
+ version: "0.3.0",
3291
+ description: "Lingyao Channel Plugin - bidirectional sync via lingyao.live server relay",
3292
+ type: "channel",
3293
+ capabilities: {
3294
+ chatTypes: ["direct"],
3295
+ media: false,
3296
+ reactions: false,
3297
+ threads: false
3298
+ },
3299
+ defaultConfig: getDefaultConfig()
3300
+ };
3301
+ var pluginEntry = {
3302
+ id: "lingyao",
3303
+ name: "Lingyao",
3304
+ description: "Lingyao Channel Plugin - bidirectional sync via lingyao.live",
3305
+ setRuntime(runtime) {
3306
+ const rt = runtime;
3307
+ setRuntime(rt);
3308
+ orchestrator = new MultiAccountOrchestrator(rt);
3309
+ },
3310
+ get plugin() {
3311
+ const rt = orchestrator?.runtime;
3312
+ if (!rt) {
3313
+ throw new Error("Runtime not set. Call setRuntime() before accessing plugin.");
3314
+ }
3315
+ return buildPluginBase(rt);
3316
+ },
3317
+ // Security and pairing options for createChatChannelPlugin composition
3318
+ security: securityOptions,
3319
+ pairing: pairingOptions
3320
+ };
3321
+ var index_default = pluginEntry;
3322
+ export {
3323
+ LINGYAO_SERVER_URL,
3324
+ LingyaoChannel,
3325
+ MessageType,
3326
+ createPlugin,
3327
+ index_default as default,
3328
+ getDefaultConfig,
3329
+ pluginMetadata,
3330
+ validateConfig
3331
+ };
3332
+ //# sourceMappingURL=index.js.map