@lingyao037/openclaw-lingyao-cli 1.3.6 → 1.5.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/dist/cli.d.ts +2 -201
- package/dist/index.d.ts +281 -16
- package/dist/index.js +291 -119
- package/dist/index.js.map +1 -1
- package/dist/probe-DW7_cF66.d.ts +212 -0
- package/dist/setup-entry.d.ts +2 -2
- package/dist/setup-entry.js +103 -78
- package/dist/setup-entry.js.map +1 -1
- package/dist/{status-CS7AsRlS.d.ts → status-n_XeBoR3.d.ts} +1 -1
- package/dist/{types-BZMU9mea.d.ts → types-BFXkMaHp.d.ts} +24 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ var globalRuntime = null;
|
|
|
11
11
|
function setRuntime(runtime) {
|
|
12
12
|
globalRuntime = runtime;
|
|
13
13
|
}
|
|
14
|
-
function adaptPluginRuntime(pr) {
|
|
14
|
+
function adaptPluginRuntime(pr, lingyaoConfig) {
|
|
15
15
|
const noop = (..._args) => {
|
|
16
16
|
};
|
|
17
17
|
const rawLogger = pr.logging?.getChildLogger?.() ?? {
|
|
@@ -28,7 +28,10 @@ function adaptPluginRuntime(pr) {
|
|
|
28
28
|
const stateDir = pr.state?.resolveStateDir?.() ?? join(process.cwd(), ".lingyao-data");
|
|
29
29
|
const storeDir = join(stateDir, "lingyao");
|
|
30
30
|
return {
|
|
31
|
-
config: {
|
|
31
|
+
config: {
|
|
32
|
+
enabled: true,
|
|
33
|
+
...lingyaoConfig
|
|
34
|
+
},
|
|
32
35
|
logger: childLogger,
|
|
33
36
|
storage: {
|
|
34
37
|
async get(key) {
|
|
@@ -63,6 +66,113 @@ function adaptPluginRuntime(pr) {
|
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
// src/config-schema.ts
|
|
70
|
+
import { z } from "zod";
|
|
71
|
+
var lingyaoDmPolicySchema = z.enum(["pairing", "allowlist", "open"]);
|
|
72
|
+
var allowFromSchema = z.array(z.string()).optional();
|
|
73
|
+
var lingyaoAccountConfigSchema = z.object({
|
|
74
|
+
enabled: z.boolean().optional(),
|
|
75
|
+
dmPolicy: lingyaoDmPolicySchema.optional(),
|
|
76
|
+
allowFrom: allowFromSchema,
|
|
77
|
+
websocketHeartbeatIntervalMs: z.number().int().min(5e3).max(12e4).optional()
|
|
78
|
+
});
|
|
79
|
+
var lingyaoAccountConfigLooseSchema = z.object({
|
|
80
|
+
enabled: z.boolean().optional(),
|
|
81
|
+
dmPolicy: z.unknown().optional(),
|
|
82
|
+
allowFrom: z.array(z.string()).optional(),
|
|
83
|
+
gatewayId: z.string().optional(),
|
|
84
|
+
websocketHeartbeatIntervalMs: z.number().optional(),
|
|
85
|
+
maxOfflineMessages: z.number().optional(),
|
|
86
|
+
tokenExpiryDays: z.number().optional()
|
|
87
|
+
}).passthrough();
|
|
88
|
+
var lingyaoConfigSchema = z.object({
|
|
89
|
+
enabled: z.boolean().default(true),
|
|
90
|
+
dmPolicy: lingyaoDmPolicySchema.optional().default("pairing"),
|
|
91
|
+
allowFrom: allowFromSchema.default([]),
|
|
92
|
+
websocketHeartbeatIntervalMs: z.number().int().min(5e3).max(12e4).optional(),
|
|
93
|
+
notificationRatePerSecond: z.number().positive().max(500).optional(),
|
|
94
|
+
notificationBurst: z.number().int().positive().max(1e4).optional(),
|
|
95
|
+
defaultAccount: z.string().min(1).optional(),
|
|
96
|
+
accounts: z.record(lingyaoAccountConfigSchema).optional()
|
|
97
|
+
});
|
|
98
|
+
var lingyaoChannelConfigSchema = {
|
|
99
|
+
schema: {
|
|
100
|
+
type: "object",
|
|
101
|
+
additionalProperties: false,
|
|
102
|
+
properties: {
|
|
103
|
+
enabled: {
|
|
104
|
+
type: "boolean",
|
|
105
|
+
default: true
|
|
106
|
+
},
|
|
107
|
+
dmPolicy: {
|
|
108
|
+
type: "string",
|
|
109
|
+
enum: ["pairing", "allowlist", "open"],
|
|
110
|
+
default: "pairing"
|
|
111
|
+
},
|
|
112
|
+
allowFrom: {
|
|
113
|
+
type: "array",
|
|
114
|
+
items: { type: "string" },
|
|
115
|
+
default: []
|
|
116
|
+
},
|
|
117
|
+
defaultAccount: {
|
|
118
|
+
type: "string"
|
|
119
|
+
},
|
|
120
|
+
websocketHeartbeatIntervalMs: {
|
|
121
|
+
type: "integer",
|
|
122
|
+
minimum: 5e3,
|
|
123
|
+
maximum: 12e4,
|
|
124
|
+
description: "WebSocket gateway_heartbeat interval in ms (default: server register response). Use 5000\u201355000 for typical relay timeout."
|
|
125
|
+
},
|
|
126
|
+
notificationRatePerSecond: {
|
|
127
|
+
type: "number",
|
|
128
|
+
minimum: 0.1,
|
|
129
|
+
maximum: 500,
|
|
130
|
+
description: "Token bucket refill rate for outbound App notifications (default 15). Override with LINGYAO_NOTIFY_RATE_PER_SEC env if runtime config is not merged."
|
|
131
|
+
},
|
|
132
|
+
notificationBurst: {
|
|
133
|
+
type: "integer",
|
|
134
|
+
minimum: 1,
|
|
135
|
+
maximum: 1e4,
|
|
136
|
+
description: "Max burst for outbound notifications (default 25). Env: LINGYAO_NOTIFY_BURST."
|
|
137
|
+
},
|
|
138
|
+
accounts: {
|
|
139
|
+
type: "object",
|
|
140
|
+
additionalProperties: false,
|
|
141
|
+
properties: {
|
|
142
|
+
enabled: {
|
|
143
|
+
type: "boolean",
|
|
144
|
+
default: true
|
|
145
|
+
},
|
|
146
|
+
dmPolicy: {
|
|
147
|
+
type: "string",
|
|
148
|
+
enum: ["pairing", "allowlist", "open"]
|
|
149
|
+
},
|
|
150
|
+
allowFrom: {
|
|
151
|
+
type: "array",
|
|
152
|
+
items: { type: "string" }
|
|
153
|
+
},
|
|
154
|
+
websocketHeartbeatIntervalMs: {
|
|
155
|
+
type: "integer",
|
|
156
|
+
minimum: 5e3,
|
|
157
|
+
maximum: 12e4
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
default: {}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
function validateConfig(config) {
|
|
166
|
+
return lingyaoConfigSchema.parse(config);
|
|
167
|
+
}
|
|
168
|
+
function getDefaultConfig() {
|
|
169
|
+
return {
|
|
170
|
+
enabled: true,
|
|
171
|
+
dmPolicy: "pairing",
|
|
172
|
+
allowFrom: []
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
66
176
|
// src/adapters/config.ts
|
|
67
177
|
function normalizeDmPolicy(raw) {
|
|
68
178
|
switch (raw) {
|
|
@@ -128,13 +238,15 @@ function createConfigAdapter() {
|
|
|
128
238
|
if (!accountConfig) {
|
|
129
239
|
throw new Error(`Account "${resolvedId}" not found`);
|
|
130
240
|
}
|
|
241
|
+
const parsed = lingyaoAccountConfigLooseSchema.safeParse(accountConfig);
|
|
242
|
+
const ac = parsed.success ? parsed.data : {};
|
|
131
243
|
return {
|
|
132
244
|
id: resolvedId,
|
|
133
245
|
accountId: resolvedId,
|
|
134
|
-
enabled:
|
|
135
|
-
dmPolicy: normalizeDmPolicy(
|
|
136
|
-
allowFrom:
|
|
137
|
-
gatewayId:
|
|
246
|
+
enabled: ac.enabled !== false,
|
|
247
|
+
dmPolicy: normalizeDmPolicy(ac.dmPolicy),
|
|
248
|
+
allowFrom: Array.isArray(ac.allowFrom) ? ac.allowFrom : [],
|
|
249
|
+
gatewayId: typeof ac.gatewayId === "string" ? ac.gatewayId : void 0,
|
|
138
250
|
rawConfig: accountConfig
|
|
139
251
|
};
|
|
140
252
|
},
|
|
@@ -764,6 +876,8 @@ var ServerHttpClient = class {
|
|
|
764
876
|
|
|
765
877
|
// src/websocket-client.ts
|
|
766
878
|
import WebSocket from "ws";
|
|
879
|
+
var LINGYAO_DEFAULT_MAX_MESSAGE_BYTES = 1048576;
|
|
880
|
+
var LINGYAO_DEFAULT_MAX_INCOMING_WS_BYTES = 2048576;
|
|
767
881
|
function isWebsocketUpgradeNotFoundError(message) {
|
|
768
882
|
return /Unexpected server response:\s*404/i.test(message) || /\b404\b/.test(message);
|
|
769
883
|
}
|
|
@@ -790,6 +904,8 @@ var LingyaoWSClient = class {
|
|
|
790
904
|
reconnectTimer = null;
|
|
791
905
|
/** When set, close handler will not schedule reconnect (e.g. HTTP 404 on upgrade). */
|
|
792
906
|
suppressReconnect = false;
|
|
907
|
+
/** 自上次成功 `open` 以来已调度的重连次数(用于退避与上限) */
|
|
908
|
+
reconnectAttempt = 0;
|
|
793
909
|
messageHandlers = /* @__PURE__ */ new Map();
|
|
794
910
|
logger;
|
|
795
911
|
constructor(runtime, config) {
|
|
@@ -853,6 +969,7 @@ var LingyaoWSClient = class {
|
|
|
853
969
|
*/
|
|
854
970
|
handleOpen() {
|
|
855
971
|
this.state = "connected";
|
|
972
|
+
this.reconnectAttempt = 0;
|
|
856
973
|
this.connectionId = this.generateConnectionId();
|
|
857
974
|
this.logger.info("WebSocket connected to Lingyao server", {
|
|
858
975
|
connectionId: this.connectionId
|
|
@@ -873,6 +990,14 @@ var LingyaoWSClient = class {
|
|
|
873
990
|
*/
|
|
874
991
|
async handleMessage(data) {
|
|
875
992
|
try {
|
|
993
|
+
const maxBytes = this.config.maxIncomingMessageBytes ?? LINGYAO_DEFAULT_MAX_INCOMING_WS_BYTES;
|
|
994
|
+
if (data.length > maxBytes) {
|
|
995
|
+
this.logger.warn("Dropped oversize WebSocket frame", {
|
|
996
|
+
bytes: data.length,
|
|
997
|
+
maxBytes
|
|
998
|
+
});
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
876
1001
|
const message = JSON.parse(data.toString());
|
|
877
1002
|
const rawType = String(message.type);
|
|
878
1003
|
const handlerKey = normalizeIncomingGatewayMessageType(rawType);
|
|
@@ -941,8 +1066,7 @@ var LingyaoWSClient = class {
|
|
|
941
1066
|
capabilities: {
|
|
942
1067
|
websocket: true,
|
|
943
1068
|
compression: false,
|
|
944
|
-
maxMessageSize:
|
|
945
|
-
// 1MB
|
|
1069
|
+
maxMessageSize: LINGYAO_DEFAULT_MAX_MESSAGE_BYTES
|
|
946
1070
|
}
|
|
947
1071
|
}
|
|
948
1072
|
};
|
|
@@ -1006,11 +1130,33 @@ var LingyaoWSClient = class {
|
|
|
1006
1130
|
if (this.reconnectTimer) {
|
|
1007
1131
|
return;
|
|
1008
1132
|
}
|
|
1009
|
-
|
|
1133
|
+
const maxAttempts = this.config.reconnectMaxAttempts ?? 0;
|
|
1134
|
+
if (maxAttempts > 0 && this.reconnectAttempt >= maxAttempts) {
|
|
1135
|
+
this.logger.error("Reconnect aborted: max attempts reached", {
|
|
1136
|
+
attempts: this.reconnectAttempt,
|
|
1137
|
+
maxAttempts
|
|
1138
|
+
});
|
|
1139
|
+
this.emitEvent({
|
|
1140
|
+
type: "reconnect_aborted",
|
|
1141
|
+
reason: "max_attempts",
|
|
1142
|
+
attempts: this.reconnectAttempt
|
|
1143
|
+
});
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
const base = this.config.reconnectInterval;
|
|
1147
|
+
const mult = this.config.reconnectBackoffMultiplier ?? 2;
|
|
1148
|
+
const cap = this.config.reconnectMaxDelayMs ?? 6e4;
|
|
1149
|
+
const pow = Math.pow(mult, this.reconnectAttempt);
|
|
1150
|
+
const delay = Math.min(base * pow, cap);
|
|
1151
|
+
this.logger.info(`Scheduling reconnect`, {
|
|
1152
|
+
delayMs: delay,
|
|
1153
|
+
attempt: this.reconnectAttempt
|
|
1154
|
+
});
|
|
1155
|
+
this.reconnectAttempt += 1;
|
|
1010
1156
|
this.reconnectTimer = setTimeout(() => {
|
|
1011
1157
|
this.reconnectTimer = null;
|
|
1012
1158
|
this.connect();
|
|
1013
|
-
},
|
|
1159
|
+
}, delay);
|
|
1014
1160
|
}
|
|
1015
1161
|
/**
|
|
1016
1162
|
* 发送消息到服务器
|
|
@@ -1091,9 +1237,9 @@ var LingyaoWSClient = class {
|
|
|
1091
1237
|
if (this.config.eventHandler) {
|
|
1092
1238
|
this.config.eventHandler({
|
|
1093
1239
|
type: "pairing_completed",
|
|
1094
|
-
deviceId: payload?.deviceId,
|
|
1095
|
-
deviceInfo: payload?.deviceInfo,
|
|
1096
|
-
sessionId: payload?.sessionId
|
|
1240
|
+
deviceId: payload?.deviceId ?? "",
|
|
1241
|
+
deviceInfo: payload?.deviceInfo ?? { name: "", platform: "", version: "" },
|
|
1242
|
+
sessionId: payload?.sessionId ?? ""
|
|
1097
1243
|
});
|
|
1098
1244
|
}
|
|
1099
1245
|
}
|
|
@@ -1183,10 +1329,13 @@ var LingyaoWSClient = class {
|
|
|
1183
1329
|
// src/accounts.ts
|
|
1184
1330
|
var STORAGE_KEY_ACCOUNTS = "lingyao:accounts";
|
|
1185
1331
|
var STORAGE_KEY_PENDING_PAIRINGS = "lingyao:pending_pairings";
|
|
1186
|
-
var AccountManager = class {
|
|
1332
|
+
var AccountManager = class _AccountManager {
|
|
1187
1333
|
runtime;
|
|
1188
1334
|
accounts = /* @__PURE__ */ new Map();
|
|
1189
1335
|
pendingPairings = /* @__PURE__ */ new Map();
|
|
1336
|
+
/** Debounced flush for high-frequency updates (e.g. heartbeat lastSeen). */
|
|
1337
|
+
accountsSaveTimer = null;
|
|
1338
|
+
static ACCOUNTS_SAVE_DEBOUNCE_MS = 3e3;
|
|
1190
1339
|
constructor(runtime) {
|
|
1191
1340
|
this.runtime = runtime;
|
|
1192
1341
|
}
|
|
@@ -1309,7 +1458,31 @@ var AccountManager = class {
|
|
|
1309
1458
|
const account = this.accounts.get(deviceId);
|
|
1310
1459
|
if (account) {
|
|
1311
1460
|
account.lastSeenAt = Date.now();
|
|
1312
|
-
|
|
1461
|
+
this.scheduleDebouncedAccountsSave();
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Persist any debounced account state immediately (e.g. before process/account shutdown).
|
|
1466
|
+
*/
|
|
1467
|
+
async flushPendingAccountsSave() {
|
|
1468
|
+
if (this.accountsSaveTimer) {
|
|
1469
|
+
clearTimeout(this.accountsSaveTimer);
|
|
1470
|
+
this.accountsSaveTimer = null;
|
|
1471
|
+
}
|
|
1472
|
+
await this.persistAccounts();
|
|
1473
|
+
}
|
|
1474
|
+
scheduleDebouncedAccountsSave() {
|
|
1475
|
+
if (this.accountsSaveTimer) {
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
this.accountsSaveTimer = setTimeout(() => {
|
|
1479
|
+
this.accountsSaveTimer = null;
|
|
1480
|
+
void this.persistAccounts().catch(
|
|
1481
|
+
(e) => this.runtime.logger.error("Failed to save accounts (debounced)", e)
|
|
1482
|
+
);
|
|
1483
|
+
}, _AccountManager.ACCOUNTS_SAVE_DEBOUNCE_MS);
|
|
1484
|
+
if (this.accountsSaveTimer.unref) {
|
|
1485
|
+
this.accountsSaveTimer.unref();
|
|
1313
1486
|
}
|
|
1314
1487
|
}
|
|
1315
1488
|
/**
|
|
@@ -1394,9 +1567,16 @@ var AccountManager = class {
|
|
|
1394
1567
|
}
|
|
1395
1568
|
}
|
|
1396
1569
|
/**
|
|
1397
|
-
* Save accounts to storage
|
|
1570
|
+
* Save accounts to storage (immediate; clears any pending debounced save).
|
|
1398
1571
|
*/
|
|
1399
1572
|
async saveAccounts() {
|
|
1573
|
+
if (this.accountsSaveTimer) {
|
|
1574
|
+
clearTimeout(this.accountsSaveTimer);
|
|
1575
|
+
this.accountsSaveTimer = null;
|
|
1576
|
+
}
|
|
1577
|
+
await this.persistAccounts();
|
|
1578
|
+
}
|
|
1579
|
+
async persistAccounts() {
|
|
1400
1580
|
const accounts = Array.from(this.accounts.values());
|
|
1401
1581
|
await this.runtime.storage.set(STORAGE_KEY_ACCOUNTS, accounts);
|
|
1402
1582
|
}
|
|
@@ -1602,6 +1782,7 @@ var Probe = class {
|
|
|
1602
1782
|
};
|
|
1603
1783
|
|
|
1604
1784
|
// src/metrics.ts
|
|
1785
|
+
var MAX_HISTOGRAM_SAMPLES_PER_NAME = 1e4;
|
|
1605
1786
|
var MetricsManager = class {
|
|
1606
1787
|
metrics = /* @__PURE__ */ new Map();
|
|
1607
1788
|
counters = /* @__PURE__ */ new Map();
|
|
@@ -1720,6 +1901,9 @@ var MetricsManager = class {
|
|
|
1720
1901
|
this.histograms.set(name, values);
|
|
1721
1902
|
}
|
|
1722
1903
|
values.push(value);
|
|
1904
|
+
if (values.length > MAX_HISTOGRAM_SAMPLES_PER_NAME) {
|
|
1905
|
+
values.splice(0, values.length - MAX_HISTOGRAM_SAMPLES_PER_NAME);
|
|
1906
|
+
}
|
|
1723
1907
|
this.recordMetric({
|
|
1724
1908
|
name,
|
|
1725
1909
|
type: "histogram" /* HISTOGRAM */,
|
|
@@ -1940,6 +2124,7 @@ var Monitor = class {
|
|
|
1940
2124
|
};
|
|
1941
2125
|
|
|
1942
2126
|
// src/errors.ts
|
|
2127
|
+
var MAX_LAST_ERROR_ENTRIES = 128;
|
|
1943
2128
|
var LingyaoError = class extends Error {
|
|
1944
2129
|
code;
|
|
1945
2130
|
severity;
|
|
@@ -2058,6 +2243,11 @@ var ErrorHandler = class {
|
|
|
2058
2243
|
error,
|
|
2059
2244
|
timestamp
|
|
2060
2245
|
});
|
|
2246
|
+
while (this.lastErrors.size > MAX_LAST_ERROR_ENTRIES) {
|
|
2247
|
+
const oldest = this.lastErrors.keys().next().value;
|
|
2248
|
+
if (oldest === void 0) break;
|
|
2249
|
+
this.lastErrors.delete(oldest);
|
|
2250
|
+
}
|
|
2061
2251
|
}
|
|
2062
2252
|
/**
|
|
2063
2253
|
* 检查错误阈值
|
|
@@ -2230,6 +2420,50 @@ var ErrorHandler = class {
|
|
|
2230
2420
|
}
|
|
2231
2421
|
};
|
|
2232
2422
|
|
|
2423
|
+
// src/notification-rate-limit.ts
|
|
2424
|
+
var NotificationRateLimiter = class {
|
|
2425
|
+
constructor(refillPerSecond, burst) {
|
|
2426
|
+
this.refillPerSecond = refillPerSecond;
|
|
2427
|
+
this.burst = burst;
|
|
2428
|
+
}
|
|
2429
|
+
buckets = /* @__PURE__ */ new Map();
|
|
2430
|
+
/**
|
|
2431
|
+
* Attempt to consume one send slot for `accountId`. Returns false if rate-limited.
|
|
2432
|
+
*/
|
|
2433
|
+
tryConsume(accountId) {
|
|
2434
|
+
const now = Date.now();
|
|
2435
|
+
let b = this.buckets.get(accountId);
|
|
2436
|
+
if (!b) {
|
|
2437
|
+
b = { tokens: this.burst, lastUpdate: now };
|
|
2438
|
+
this.buckets.set(accountId, b);
|
|
2439
|
+
}
|
|
2440
|
+
const elapsedSec = (now - b.lastUpdate) / 1e3;
|
|
2441
|
+
if (elapsedSec > 0) {
|
|
2442
|
+
b.tokens = Math.min(this.burst, b.tokens + elapsedSec * this.refillPerSecond);
|
|
2443
|
+
b.lastUpdate = now;
|
|
2444
|
+
}
|
|
2445
|
+
if (b.tokens >= 1) {
|
|
2446
|
+
b.tokens -= 1;
|
|
2447
|
+
return true;
|
|
2448
|
+
}
|
|
2449
|
+
return false;
|
|
2450
|
+
}
|
|
2451
|
+
};
|
|
2452
|
+
var DEFAULT_NOTIFY_REFILL_PER_SEC = 15;
|
|
2453
|
+
var DEFAULT_NOTIFY_BURST = 25;
|
|
2454
|
+
function resolveNotificationLimiterParams(cfg) {
|
|
2455
|
+
const envRef = typeof process !== "undefined" ? process.env.LINGYAO_NOTIFY_RATE_PER_SEC : void 0;
|
|
2456
|
+
const envBurst = typeof process !== "undefined" ? process.env.LINGYAO_NOTIFY_BURST : void 0;
|
|
2457
|
+
const refillFromEnv = envRef !== void 0 && envRef !== "" ? Number(envRef) : Number.NaN;
|
|
2458
|
+
const burstFromEnv = envBurst !== void 0 && envBurst !== "" ? Number(envBurst) : Number.NaN;
|
|
2459
|
+
const refill = cfg.notificationRatePerSecond ?? (!Number.isNaN(refillFromEnv) && refillFromEnv > 0 ? refillFromEnv : void 0) ?? DEFAULT_NOTIFY_REFILL_PER_SEC;
|
|
2460
|
+
const burst = cfg.notificationBurst ?? (!Number.isNaN(burstFromEnv) && burstFromEnv > 0 ? Math.floor(burstFromEnv) : void 0) ?? DEFAULT_NOTIFY_BURST;
|
|
2461
|
+
return {
|
|
2462
|
+
refillPerSecond: Math.min(500, Math.max(0.1, refill)),
|
|
2463
|
+
burst: Math.min(1e4, Math.max(1, Math.floor(burst)))
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2233
2467
|
// src/orchestrator.ts
|
|
2234
2468
|
function getMachineId() {
|
|
2235
2469
|
try {
|
|
@@ -2247,8 +2481,11 @@ function generateGatewayId(accountId) {
|
|
|
2247
2481
|
var MultiAccountOrchestrator = class {
|
|
2248
2482
|
runtime;
|
|
2249
2483
|
accounts = /* @__PURE__ */ new Map();
|
|
2484
|
+
notificationLimiter;
|
|
2250
2485
|
constructor(runtime) {
|
|
2251
2486
|
this.runtime = runtime;
|
|
2487
|
+
const lim = resolveNotificationLimiterParams(runtime.config);
|
|
2488
|
+
this.notificationLimiter = new NotificationRateLimiter(lim.refillPerSecond, lim.burst);
|
|
2252
2489
|
}
|
|
2253
2490
|
/**
|
|
2254
2491
|
* Start an account: create components, register to server, connect WS.
|
|
@@ -2351,6 +2588,11 @@ var MultiAccountOrchestrator = class {
|
|
|
2351
2588
|
if (state.httpClient) {
|
|
2352
2589
|
state.httpClient.stopHeartbeat();
|
|
2353
2590
|
}
|
|
2591
|
+
try {
|
|
2592
|
+
await state.accountManager.flushPendingAccountsSave();
|
|
2593
|
+
} catch (e) {
|
|
2594
|
+
this.runtime.logger.error(`[${accountId}] Failed to flush account storage`, e);
|
|
2595
|
+
}
|
|
2354
2596
|
state.status = "stopped";
|
|
2355
2597
|
this.runtime.logger.info(`Account "${accountId}" stopped`);
|
|
2356
2598
|
}
|
|
@@ -2407,6 +2649,12 @@ var MultiAccountOrchestrator = class {
|
|
|
2407
2649
|
this.runtime.logger.warn(`[${accountId}] Cannot send notification: WS not connected`, { deviceId });
|
|
2408
2650
|
return false;
|
|
2409
2651
|
}
|
|
2652
|
+
if (!this.notificationLimiter.tryConsume(accountId)) {
|
|
2653
|
+
this.runtime.logger.warn(`[${accountId}] Notification rate limited (relay protection)`, {
|
|
2654
|
+
deviceId
|
|
2655
|
+
});
|
|
2656
|
+
return false;
|
|
2657
|
+
}
|
|
2410
2658
|
this.runtime.logger.info(`[${accountId}] Sending notification to device`, {
|
|
2411
2659
|
deviceId,
|
|
2412
2660
|
notificationType: notification.type
|
|
@@ -2439,7 +2687,7 @@ var MultiAccountOrchestrator = class {
|
|
|
2439
2687
|
const response = await state.httpClient.register({
|
|
2440
2688
|
websocket: true,
|
|
2441
2689
|
compression: false,
|
|
2442
|
-
maxMessageSize:
|
|
2690
|
+
maxMessageSize: LINGYAO_DEFAULT_MAX_MESSAGE_BYTES
|
|
2443
2691
|
});
|
|
2444
2692
|
this.runtime.logger.info(`Account "${state.accountId}": registered successfully`, {
|
|
2445
2693
|
expiresAt: new Date(response.expiresAt).toISOString()
|
|
@@ -2466,17 +2714,14 @@ var MultiAccountOrchestrator = class {
|
|
|
2466
2714
|
deviceId,
|
|
2467
2715
|
messageType: msg.type
|
|
2468
2716
|
});
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
break;
|
|
2478
|
-
default:
|
|
2479
|
-
this.runtime.logger.warn(`[${state.accountId}] Unknown message type`, { type: msg.type });
|
|
2717
|
+
if (msg.type === "sync_diary" || msg.type === "sync_memory") {
|
|
2718
|
+
const syncBody = msg;
|
|
2719
|
+
await this.handleSyncMessage(state, deviceId, syncBody);
|
|
2720
|
+
} else if (msg.type === "heartbeat") {
|
|
2721
|
+
await state.accountManager.updateLastSeen(deviceId);
|
|
2722
|
+
state.monitor.recordEvent("heartbeat_received" /* HEARTBEAT_RECEIVED */, { deviceId });
|
|
2723
|
+
} else {
|
|
2724
|
+
this.runtime.logger.warn(`[${state.accountId}] Unknown message type`, { type: msg.type });
|
|
2480
2725
|
}
|
|
2481
2726
|
} catch (error) {
|
|
2482
2727
|
this.runtime.logger.error(`[${state.accountId}] Error handling App message`, error);
|
|
@@ -2609,12 +2854,20 @@ var MultiAccountOrchestrator = class {
|
|
|
2609
2854
|
break;
|
|
2610
2855
|
case "fatal_handshake":
|
|
2611
2856
|
if (event.reason === "http_404") {
|
|
2612
|
-
|
|
2857
|
+
this.handleWsHandshake404(state).catch((err) => {
|
|
2858
|
+
this.runtime.logger.error(`[${state.accountId}] WS 404 recovery (async) failed`, err);
|
|
2859
|
+
});
|
|
2613
2860
|
}
|
|
2614
2861
|
break;
|
|
2615
2862
|
case "pairing_completed":
|
|
2616
2863
|
this.handlePairingCompleted(state, event);
|
|
2617
2864
|
break;
|
|
2865
|
+
case "reconnect_aborted":
|
|
2866
|
+
this.runtime.logger.error(`[${state.accountId}] WebSocket reconnect aborted`, {
|
|
2867
|
+
reason: event.reason,
|
|
2868
|
+
attempts: event.attempts
|
|
2869
|
+
});
|
|
2870
|
+
break;
|
|
2618
2871
|
}
|
|
2619
2872
|
};
|
|
2620
2873
|
}
|
|
@@ -2657,7 +2910,7 @@ var MultiAccountOrchestrator = class {
|
|
|
2657
2910
|
const response = await httpClient.register({
|
|
2658
2911
|
websocket: true,
|
|
2659
2912
|
compression: false,
|
|
2660
|
-
maxMessageSize:
|
|
2913
|
+
maxMessageSize: LINGYAO_DEFAULT_MAX_MESSAGE_BYTES
|
|
2661
2914
|
});
|
|
2662
2915
|
wsClient.updateToken(response.gatewayToken);
|
|
2663
2916
|
await wsClient.connect();
|
|
@@ -2686,7 +2939,7 @@ var MultiAccountOrchestrator = class {
|
|
|
2686
2939
|
const response = await state.httpClient.register({
|
|
2687
2940
|
websocket: true,
|
|
2688
2941
|
compression: false,
|
|
2689
|
-
maxMessageSize:
|
|
2942
|
+
maxMessageSize: LINGYAO_DEFAULT_MAX_MESSAGE_BYTES
|
|
2690
2943
|
});
|
|
2691
2944
|
this.runtime.logger.info(`[${state.accountId}] Obtained new token. Reconnecting WS...`);
|
|
2692
2945
|
state.wsClient.updateToken(response.gatewayToken);
|
|
@@ -2698,90 +2951,6 @@ var MultiAccountOrchestrator = class {
|
|
|
2698
2951
|
}
|
|
2699
2952
|
};
|
|
2700
2953
|
|
|
2701
|
-
// src/config-schema.ts
|
|
2702
|
-
import { z } from "zod";
|
|
2703
|
-
var lingyaoDmPolicySchema = z.enum(["pairing", "allowlist", "open"]);
|
|
2704
|
-
var allowFromSchema = z.array(z.string()).optional();
|
|
2705
|
-
var lingyaoAccountConfigSchema = z.object({
|
|
2706
|
-
enabled: z.boolean().optional(),
|
|
2707
|
-
dmPolicy: lingyaoDmPolicySchema.optional(),
|
|
2708
|
-
allowFrom: allowFromSchema,
|
|
2709
|
-
websocketHeartbeatIntervalMs: z.number().int().min(5e3).max(12e4).optional()
|
|
2710
|
-
});
|
|
2711
|
-
var lingyaoConfigSchema = z.object({
|
|
2712
|
-
enabled: z.boolean().default(true),
|
|
2713
|
-
dmPolicy: lingyaoDmPolicySchema.optional().default("pairing"),
|
|
2714
|
-
allowFrom: allowFromSchema.default([]),
|
|
2715
|
-
websocketHeartbeatIntervalMs: z.number().int().min(5e3).max(12e4).optional(),
|
|
2716
|
-
defaultAccount: z.string().min(1).optional(),
|
|
2717
|
-
accounts: z.record(lingyaoAccountConfigSchema).optional()
|
|
2718
|
-
});
|
|
2719
|
-
var lingyaoChannelConfigSchema = {
|
|
2720
|
-
schema: {
|
|
2721
|
-
type: "object",
|
|
2722
|
-
additionalProperties: false,
|
|
2723
|
-
properties: {
|
|
2724
|
-
enabled: {
|
|
2725
|
-
type: "boolean",
|
|
2726
|
-
default: true
|
|
2727
|
-
},
|
|
2728
|
-
dmPolicy: {
|
|
2729
|
-
type: "string",
|
|
2730
|
-
enum: ["pairing", "allowlist", "open"],
|
|
2731
|
-
default: "pairing"
|
|
2732
|
-
},
|
|
2733
|
-
allowFrom: {
|
|
2734
|
-
type: "array",
|
|
2735
|
-
items: { type: "string" },
|
|
2736
|
-
default: []
|
|
2737
|
-
},
|
|
2738
|
-
defaultAccount: {
|
|
2739
|
-
type: "string"
|
|
2740
|
-
},
|
|
2741
|
-
websocketHeartbeatIntervalMs: {
|
|
2742
|
-
type: "integer",
|
|
2743
|
-
minimum: 5e3,
|
|
2744
|
-
maximum: 12e4,
|
|
2745
|
-
description: "WebSocket gateway_heartbeat interval in ms (default: server register response). Use 5000\u201355000 for typical relay timeout."
|
|
2746
|
-
},
|
|
2747
|
-
accounts: {
|
|
2748
|
-
type: "object",
|
|
2749
|
-
additionalProperties: false,
|
|
2750
|
-
properties: {
|
|
2751
|
-
enabled: {
|
|
2752
|
-
type: "boolean",
|
|
2753
|
-
default: true
|
|
2754
|
-
},
|
|
2755
|
-
dmPolicy: {
|
|
2756
|
-
type: "string",
|
|
2757
|
-
enum: ["pairing", "allowlist", "open"]
|
|
2758
|
-
},
|
|
2759
|
-
allowFrom: {
|
|
2760
|
-
type: "array",
|
|
2761
|
-
items: { type: "string" }
|
|
2762
|
-
},
|
|
2763
|
-
websocketHeartbeatIntervalMs: {
|
|
2764
|
-
type: "integer",
|
|
2765
|
-
minimum: 5e3,
|
|
2766
|
-
maximum: 12e4
|
|
2767
|
-
}
|
|
2768
|
-
},
|
|
2769
|
-
default: {}
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
};
|
|
2774
|
-
function validateConfig(config) {
|
|
2775
|
-
return lingyaoConfigSchema.parse(config);
|
|
2776
|
-
}
|
|
2777
|
-
function getDefaultConfig() {
|
|
2778
|
-
return {
|
|
2779
|
-
enabled: true,
|
|
2780
|
-
dmPolicy: "pairing",
|
|
2781
|
-
allowFrom: []
|
|
2782
|
-
};
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
2954
|
// src/api.ts
|
|
2786
2955
|
var orchestrator = null;
|
|
2787
2956
|
function getOrchestrator() {
|
|
@@ -2865,15 +3034,18 @@ var lingyaoPlugin = {
|
|
|
2865
3034
|
directory: directoryAdapter,
|
|
2866
3035
|
messaging: messagingAdapter
|
|
2867
3036
|
};
|
|
2868
|
-
function initializeLingyaoRuntime(runtime) {
|
|
2869
|
-
const adapted = adaptPluginRuntime(
|
|
3037
|
+
function initializeLingyaoRuntime(runtime, options) {
|
|
3038
|
+
const adapted = adaptPluginRuntime(
|
|
3039
|
+
runtime,
|
|
3040
|
+
options?.lingyaoConfig
|
|
3041
|
+
);
|
|
2870
3042
|
setRuntime(adapted);
|
|
2871
3043
|
orchestrator = new MultiAccountOrchestrator(adapted);
|
|
2872
3044
|
}
|
|
2873
3045
|
|
|
2874
3046
|
// src/runtime-api.ts
|
|
2875
|
-
function setLingyaoRuntime(runtime) {
|
|
2876
|
-
initializeLingyaoRuntime(runtime);
|
|
3047
|
+
function setLingyaoRuntime(runtime, options) {
|
|
3048
|
+
initializeLingyaoRuntime(runtime, options);
|
|
2877
3049
|
}
|
|
2878
3050
|
|
|
2879
3051
|
// src/index.ts
|