@lingyao037/openclaw-lingyao-cli 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.mjs CHANGED
@@ -31,6 +31,46 @@ const RED = '\x1b[31m';
31
31
  const CYAN = '\x1b[36m';
32
32
  const NC = '\x1b[0m';
33
33
 
34
+ /**
35
+ * 获取机器的稳定标识符 (基于 MAC 地址)
36
+ */
37
+ function getMachineId() {
38
+ try {
39
+ const interfaces = os.networkInterfaces();
40
+ let macStr = '';
41
+
42
+ for (const name of Object.keys(interfaces)) {
43
+ const iface = interfaces[name];
44
+ if (!iface) continue;
45
+
46
+ for (const alias of iface) {
47
+ if (!alias.internal && alias.mac && alias.mac !== '00:00:00:00:00:00') {
48
+ macStr += alias.mac;
49
+ }
50
+ }
51
+ }
52
+
53
+ if (macStr) {
54
+ import('crypto').then(crypto => {
55
+ // this is async, but we want sync, so we just use require if possible
56
+ });
57
+ // fallback to simple hash since we can't use top level await here easily in older nodes
58
+ let hash = 0;
59
+ for (let i = 0; i < macStr.length; i++) {
60
+ const char = macStr.charCodeAt(i);
61
+ hash = ((hash << 5) - hash) + char;
62
+ hash = hash & hash;
63
+ }
64
+ return Math.abs(hash).toString(36).substring(0, 8);
65
+ }
66
+ } catch (e) {
67
+ // ignore
68
+ }
69
+ return Math.random().toString(36).substring(2, 10);
70
+ }
71
+
72
+ const MACHINE_ID = getMachineId();
73
+
34
74
  class LingyaoInstaller {
35
75
  constructor(customPath = null, skipDeps = true) {
36
76
  this.openclawPath = null;
@@ -444,11 +484,10 @@ class LingyaoInstaller {
444
484
  });
445
485
  }
446
486
 
447
- // Generate a stable gateway ID
487
+ // 生成稳定的网关 ID
448
488
  generateGatewayId() {
449
489
  const host = os.hostname().split('.')[0].replace(/[^a-z0-9]/gi, '').toLowerCase();
450
- const suffix = Math.random().toString(36).substring(2, 8);
451
- return `gw_openclaw_${host}_default_${suffix}`;
490
+ return `gw_openclaw_${host}_${MACHINE_ID}_default`;
452
491
  }
453
492
 
454
493
  // Pair device flow: register gateway, init pairing, show QR, poll status
package/dist/index.d.ts CHANGED
@@ -39,7 +39,11 @@ declare class ErrorHandler {
39
39
  private errorCounts;
40
40
  private lastErrors;
41
41
  private errorThresholds;
42
+ private cleanupTimer;
43
+ private errorTimestamps;
42
44
  constructor(runtime: LingyaoRuntime);
45
+ destroy(): void;
46
+ private cleanupOldErrors;
43
47
  /**
44
48
  * 处理错误
45
49
  */
@@ -604,6 +608,10 @@ declare class LingyaoWSClient {
604
608
  * 发送事件
605
609
  */
606
610
  private emitEvent;
611
+ /**
612
+ * 更新 WebSocket 连接使用的 token
613
+ */
614
+ updateToken(token: string): void;
607
615
  /**
608
616
  * 断开连接
609
617
  */
package/dist/index.js CHANGED
@@ -344,7 +344,8 @@ function createSetupAdapter() {
344
344
  }
345
345
 
346
346
  // src/orchestrator.ts
347
- import { hostname } from "os";
347
+ import { hostname, networkInterfaces } from "os";
348
+ import { createHash as createHash2 } from "crypto";
348
349
 
349
350
  // src/types.ts
350
351
  var LINGYAO_SERVER_URL = "https://api.lingyao.live";
@@ -762,6 +763,10 @@ var LingyaoWSClient = class {
762
763
  this.connectionId = null;
763
764
  this.stopHeartbeat();
764
765
  this.emitEvent({ type: "disconnected", code, reason });
766
+ if (code === 1008) {
767
+ this.logger.error("WebSocket closed with 1008 (Invalid Token). Stopping reconnect loop.");
768
+ return;
769
+ }
765
770
  if (code !== 1e3) {
766
771
  this.scheduleReconnect();
767
772
  }
@@ -956,6 +961,12 @@ var LingyaoWSClient = class {
956
961
  this.config.eventHandler(event);
957
962
  }
958
963
  }
964
+ /**
965
+ * 更新 WebSocket 连接使用的 token
966
+ */
967
+ updateToken(token) {
968
+ this.config.token = token;
969
+ }
959
970
  /**
960
971
  * 断开连接
961
972
  */
@@ -1661,9 +1672,12 @@ var MessageProcessor = class {
1661
1672
  markMessageSeen(messageId) {
1662
1673
  this.seenMessages.add(messageId);
1663
1674
  if (this.seenMessages.size > 1e4) {
1664
- const entries = Array.from(this.seenMessages);
1665
- for (let i = 0; i < entries.length / 2; i++) {
1666
- this.seenMessages.delete(entries[i]);
1675
+ let count = 0;
1676
+ const targetDeletes = Math.floor(this.seenMessages.size / 2);
1677
+ for (const id of this.seenMessages) {
1678
+ this.seenMessages.delete(id);
1679
+ count++;
1680
+ if (count >= targetDeletes) break;
1667
1681
  }
1668
1682
  }
1669
1683
  }
@@ -2009,12 +2023,14 @@ var MetricsManager = class {
2009
2023
  * 记录指标
2010
2024
  */
2011
2025
  recordMetric(data) {
2012
- this.metrics.set(`${data.name}_${Date.now()}`, data);
2026
+ const key = `${data.name}_${Date.now()}_${Math.random().toString(36).substring(7)}`;
2027
+ this.metrics.set(key, data);
2013
2028
  if (this.metrics.size > 1e4) {
2014
- const entries = Array.from(this.metrics.entries());
2015
- entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
2016
- for (let i = 0; i < 1e3; i++) {
2017
- this.metrics.delete(entries[i][0]);
2029
+ let count = 0;
2030
+ const deleteCount = Math.floor(this.metrics.size * 0.1);
2031
+ for (const k of this.metrics.keys()) {
2032
+ this.metrics.delete(k);
2033
+ if (++count >= deleteCount) break;
2018
2034
  }
2019
2035
  }
2020
2036
  }
@@ -2268,12 +2284,39 @@ var ErrorHandler = class {
2268
2284
  errorCounts = /* @__PURE__ */ new Map();
2269
2285
  lastErrors = /* @__PURE__ */ new Map();
2270
2286
  errorThresholds = /* @__PURE__ */ new Map();
2287
+ cleanupTimer = null;
2288
+ errorTimestamps = /* @__PURE__ */ new Map();
2271
2289
  constructor(runtime) {
2272
2290
  this.runtime = runtime;
2291
+ this.cleanupTimer = setInterval(() => this.cleanupOldErrors(), 5 * 60 * 1e3);
2292
+ if (this.cleanupTimer.unref) {
2293
+ this.cleanupTimer.unref();
2294
+ }
2273
2295
  this.setErrorThreshold("CONNECTION_ERROR", { count: 5, window: 6e4 });
2274
2296
  this.setErrorThreshold("AUTHENTICATION_ERROR", { count: 3, window: 6e4 });
2275
2297
  this.setErrorThreshold("NETWORK_ERROR", { count: 10, window: 6e4 });
2276
2298
  }
2299
+ destroy() {
2300
+ if (this.cleanupTimer) {
2301
+ clearInterval(this.cleanupTimer);
2302
+ this.cleanupTimer = null;
2303
+ }
2304
+ }
2305
+ cleanupOldErrors() {
2306
+ const now = Date.now();
2307
+ for (const [code, timestamps] of this.errorTimestamps.entries()) {
2308
+ const thresholdConfig = this.errorThresholds.get(code);
2309
+ const windowMs = thresholdConfig?.window || 36e5;
2310
+ const validTimestamps = timestamps.filter((ts) => now - ts <= windowMs);
2311
+ if (validTimestamps.length === 0) {
2312
+ this.errorTimestamps.delete(code);
2313
+ this.errorCounts.delete(code);
2314
+ } else {
2315
+ this.errorTimestamps.set(code, validTimestamps);
2316
+ this.errorCounts.set(code, validTimestamps.length);
2317
+ }
2318
+ }
2319
+ }
2277
2320
  /**
2278
2321
  * 处理错误
2279
2322
  */
@@ -2299,18 +2342,18 @@ var ErrorHandler = class {
2299
2342
  */
2300
2343
  recordError(error) {
2301
2344
  const key = error.code;
2302
- const count = (this.errorCounts.get(key) || 0) + 1;
2303
- this.errorCounts.set(key, count);
2345
+ const timestamp = Date.now();
2346
+ const thresholdConfig = this.errorThresholds.get(key);
2347
+ const windowMs = thresholdConfig?.window || 36e5;
2348
+ let timestamps = this.errorTimestamps.get(key) || [];
2349
+ timestamps = timestamps.filter((ts) => timestamp - ts <= windowMs);
2350
+ timestamps.push(timestamp);
2351
+ this.errorTimestamps.set(key, timestamps);
2352
+ this.errorCounts.set(key, timestamps.length);
2304
2353
  this.lastErrors.set(key, {
2305
2354
  error,
2306
- timestamp: Date.now()
2355
+ timestamp
2307
2356
  });
2308
- setTimeout(() => {
2309
- const lastError = this.lastErrors.get(key);
2310
- if (lastError && lastError.timestamp === error.timestamp) {
2311
- this.errorCounts.set(key, (this.errorCounts.get(key) || 0) - 1);
2312
- }
2313
- }, 36e5);
2314
2357
  }
2315
2358
  /**
2316
2359
  * 检查错误阈值
@@ -2484,10 +2527,30 @@ var ErrorHandler = class {
2484
2527
  };
2485
2528
 
2486
2529
  // src/orchestrator.ts
2530
+ function getMachineId() {
2531
+ try {
2532
+ const interfaces = networkInterfaces();
2533
+ let macStr = "";
2534
+ for (const name of Object.keys(interfaces)) {
2535
+ const iface = interfaces[name];
2536
+ if (!iface) continue;
2537
+ for (const alias of iface) {
2538
+ if (!alias.internal && alias.mac && alias.mac !== "00:00:00:00:00:00") {
2539
+ macStr += alias.mac;
2540
+ }
2541
+ }
2542
+ }
2543
+ if (macStr) {
2544
+ return createHash2("md5").update(macStr).digest("hex").substring(0, 8);
2545
+ }
2546
+ } catch (e) {
2547
+ }
2548
+ return Math.random().toString(36).substring(2, 10);
2549
+ }
2550
+ var MACHINE_ID = getMachineId();
2487
2551
  function generateGatewayId(accountId) {
2488
2552
  const host = hostname().split(".")[0].replace(/[^a-z0-9]/gi, "").toLowerCase();
2489
- const suffix = Math.random().toString(36).substring(2, 8);
2490
- return `gw_openclaw_${host}_${accountId}_${suffix}`;
2553
+ return `gw_openclaw_${host}_${MACHINE_ID}_${accountId}`;
2491
2554
  }
2492
2555
  var MultiAccountOrchestrator = class {
2493
2556
  runtime;
@@ -2767,6 +2830,12 @@ var MultiAccountOrchestrator = class {
2767
2830
  code: event.code,
2768
2831
  reason: event.reason
2769
2832
  });
2833
+ if (event.code === 1008) {
2834
+ this.runtime.logger.warn(`[${state.accountId}] Token invalid (1008). Forcing re-registration...`);
2835
+ this.handleInvalidToken(state).catch((err) => {
2836
+ this.runtime.logger.error(`[${state.accountId}] Failed to re-register after 1008`, err);
2837
+ });
2838
+ }
2770
2839
  break;
2771
2840
  case "error":
2772
2841
  this.runtime.logger.error(`[${state.accountId}] WS error`, event.error);
@@ -2799,13 +2868,36 @@ var MultiAccountOrchestrator = class {
2799
2868
  this.runtime.logger.error(`[${state.accountId}] Failed to auto-bind device: ${deviceId}`, error);
2800
2869
  }
2801
2870
  }
2871
+ /**
2872
+ * Handle invalid token by re-registering the gateway and reconnecting WS
2873
+ */
2874
+ async handleInvalidToken(state) {
2875
+ if (!state.httpClient || !state.wsClient) {
2876
+ return;
2877
+ }
2878
+ try {
2879
+ this.runtime.logger.info(`[${state.accountId}] Requesting new gateway token...`);
2880
+ const response = await state.httpClient.register({
2881
+ websocket: true,
2882
+ compression: false,
2883
+ maxMessageSize: 1048576
2884
+ });
2885
+ this.runtime.logger.info(`[${state.accountId}] Obtained new token. Reconnecting WS...`);
2886
+ state.wsClient.updateToken(response.gatewayToken);
2887
+ await state.wsClient.connect();
2888
+ } catch (error) {
2889
+ this.runtime.logger.error(`[${state.accountId}] Failed to handle invalid token`, error);
2890
+ state.errorHandler.handleError(error);
2891
+ }
2892
+ }
2802
2893
  };
2803
2894
 
2804
2895
  // src/channel.ts
2805
- import { hostname as hostname2 } from "os";
2896
+ import { createHash as createHash4 } from "crypto";
2897
+ import { hostname as hostname2, networkInterfaces as networkInterfaces2 } from "os";
2806
2898
 
2807
2899
  // src/token.ts
2808
- import { randomBytes as randomBytes2, createHash as createHash2, timingSafeEqual as timingSafeEqual2 } from "crypto";
2900
+ import { randomBytes as randomBytes2, createHash as createHash3, timingSafeEqual as timingSafeEqual2 } from "crypto";
2809
2901
  var TokenManager = class {
2810
2902
  constructor(_runtime) {
2811
2903
  void _runtime;
@@ -2921,7 +3013,7 @@ var TokenManager = class {
2921
3013
  * Sign data with secret
2922
3014
  */
2923
3015
  sign(data, secret) {
2924
- return createHash2("sha256").update(data + secret).digest("base64");
3016
+ return createHash3("sha256").update(data + secret).digest("base64");
2925
3017
  }
2926
3018
  /**
2927
3019
  * Base64URL encode without padding
@@ -2932,6 +3024,27 @@ var TokenManager = class {
2932
3024
  };
2933
3025
 
2934
3026
  // src/channel.ts
3027
+ function getMachineId2() {
3028
+ try {
3029
+ const interfaces = networkInterfaces2();
3030
+ let macStr = "";
3031
+ for (const name of Object.keys(interfaces)) {
3032
+ const iface = interfaces[name];
3033
+ if (!iface) continue;
3034
+ for (const alias of iface) {
3035
+ if (!alias.internal && alias.mac && alias.mac !== "00:00:00:00:00:00") {
3036
+ macStr += alias.mac;
3037
+ }
3038
+ }
3039
+ }
3040
+ if (macStr) {
3041
+ return createHash4("md5").update(macStr).digest("hex").substring(0, 8);
3042
+ }
3043
+ } catch (e) {
3044
+ }
3045
+ return Math.random().toString(36).substring(2, 10);
3046
+ }
3047
+ var MACHINE_ID2 = getMachineId2();
2935
3048
  var LingyaoChannel = class {
2936
3049
  runtime;
2937
3050
  config;
@@ -3313,8 +3426,7 @@ var LingyaoChannel = class {
3313
3426
  */
3314
3427
  generateGatewayId() {
3315
3428
  const hostname3 = this.getRuntimeHostname();
3316
- const randomSuffix = Math.random().toString(36).substring(2, 8);
3317
- return `gw_openclaw_${hostname3}_${randomSuffix}`;
3429
+ return `gw_openclaw_${hostname3}_${MACHINE_ID2}`;
3318
3430
  }
3319
3431
  /**
3320
3432
  * 获取运行时主机名