@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 +42 -3
- package/dist/index.d.ts +8 -0
- package/dist/index.js +137 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
//
|
|
487
|
+
// 生成稳定的网关 ID
|
|
448
488
|
generateGatewayId() {
|
|
449
489
|
const host = os.hostname().split('.')[0].replace(/[^a-z0-9]/gi, '').toLowerCase();
|
|
450
|
-
|
|
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
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2015
|
-
|
|
2016
|
-
for (
|
|
2017
|
-
this.metrics.delete(
|
|
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
|
|
2303
|
-
this.
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
3317
|
-
return `gw_openclaw_${hostname3}_${randomSuffix}`;
|
|
3429
|
+
return `gw_openclaw_${hostname3}_${MACHINE_ID2}`;
|
|
3318
3430
|
}
|
|
3319
3431
|
/**
|
|
3320
3432
|
* 获取运行时主机名
|