@lingyao037/openclaw-lingyao-cli 1.3.6 → 1.4.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 +11 -2
- package/dist/index.d.ts +4 -23
- package/dist/index.js +254 -113
- package/dist/index.js.map +1 -1
- package/dist/setup-entry.d.ts +2 -2
- package/dist/setup-entry.js +89 -78
- package/dist/setup-entry.js.map +1 -1
- package/dist/{status-CS7AsRlS.d.ts → status-Bjzb8u67.d.ts} +1 -1
- package/dist/{types-BZMU9mea.d.ts → types-XJDsfTRA.d.ts} +15 -1
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as LingyaoRuntime, a as LingyaoAccount, D as DeviceToken, b as DeviceInfo, H as HealthStatus } from './types-
|
|
1
|
+
import { L as LingyaoRuntime, a as LingyaoAccount, D as DeviceToken, b as DeviceInfo, H as HealthStatus } from './types-XJDsfTRA.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Account storage and management
|
|
@@ -7,6 +7,9 @@ declare class AccountManager {
|
|
|
7
7
|
private runtime;
|
|
8
8
|
private accounts;
|
|
9
9
|
private pendingPairings;
|
|
10
|
+
/** Debounced flush for high-frequency updates (e.g. heartbeat lastSeen). */
|
|
11
|
+
private accountsSaveTimer;
|
|
12
|
+
private static readonly ACCOUNTS_SAVE_DEBOUNCE_MS;
|
|
10
13
|
constructor(runtime: LingyaoRuntime);
|
|
11
14
|
/**
|
|
12
15
|
* Initialize account manager from storage
|
|
@@ -44,6 +47,11 @@ declare class AccountManager {
|
|
|
44
47
|
* Update account's last seen timestamp
|
|
45
48
|
*/
|
|
46
49
|
updateLastSeen(deviceId: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Persist any debounced account state immediately (e.g. before process/account shutdown).
|
|
52
|
+
*/
|
|
53
|
+
flushPendingAccountsSave(): Promise<void>;
|
|
54
|
+
private scheduleDebouncedAccountsSave;
|
|
47
55
|
/**
|
|
48
56
|
* Revoke an account
|
|
49
57
|
*/
|
|
@@ -63,9 +71,10 @@ declare class AccountManager {
|
|
|
63
71
|
*/
|
|
64
72
|
cleanupExpired(): Promise<void>;
|
|
65
73
|
/**
|
|
66
|
-
* Save accounts to storage
|
|
74
|
+
* Save accounts to storage (immediate; clears any pending debounced save).
|
|
67
75
|
*/
|
|
68
76
|
private saveAccounts;
|
|
77
|
+
private persistAccounts;
|
|
69
78
|
/**
|
|
70
79
|
* Save pending pairings to storage
|
|
71
80
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1,27 +1,8 @@
|
|
|
1
|
-
import { R as ResolvedAccount, L as LingyaoProbeResult } from './status-
|
|
1
|
+
import { R as ResolvedAccount, L as LingyaoProbeResult } from './status-Bjzb8u67.js';
|
|
2
2
|
import * as openclaw_plugin_sdk from 'openclaw/plugin-sdk';
|
|
3
3
|
import { PluginRuntime, ChannelPlugin } from 'openclaw/plugin-sdk';
|
|
4
|
-
import { c as LingyaoConfig } from './types-
|
|
5
|
-
export { A as AckRequest, b as DeviceInfo, D as DeviceToken,
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Agent message types — used for type-safe message passing.
|
|
9
|
-
*
|
|
10
|
-
* NOTE: MessageProcessor has been removed. Inbound messages are now dispatched
|
|
11
|
-
* directly via SDK's dispatchInboundDirectDmWithRuntime in orchestrator.ts.
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* Agent message format - messages passed to Agent
|
|
15
|
-
*/
|
|
16
|
-
interface AgentMessage {
|
|
17
|
-
id: string;
|
|
18
|
-
type: "diary" | "memory" | "heartbeat";
|
|
19
|
-
from: string;
|
|
20
|
-
deviceId: string;
|
|
21
|
-
content: string;
|
|
22
|
-
metadata: Record<string, unknown>;
|
|
23
|
-
timestamp: number;
|
|
24
|
-
}
|
|
4
|
+
import { c as LingyaoConfig } from './types-XJDsfTRA.js';
|
|
5
|
+
export { A as AckRequest, d as AgentMessage, b as DeviceInfo, D as DeviceToken, e as DiarySyncPayload, F as FailedEntry, H as HealthStatus, f as LINGYAO_SERVER_URL, a as LingyaoAccount, g as LingyaoAccountConfig, h as LingyaoMessage, L as LingyaoRuntime, M as MemorySyncPayload, i as MessageHandler, j as MessageType, N as NotifyAction, k as NotifyPayload, l as NotifyRequest, P as PairingCode, m as PairingConfirmRequest, n as PairingConfirmResponse, o as PollRequest, p as PollResponse, Q as QueuedMessage, S as SyncRequest, q as SyncResponse, T as TokenRefreshRequest, r as TokenRefreshResponse, W as WebSocketConnection, s as getLingyaoGatewayWsUrl } from './types-XJDsfTRA.js';
|
|
25
6
|
|
|
26
7
|
/**
|
|
27
8
|
* Validate configuration object
|
|
@@ -45,4 +26,4 @@ declare const plugin: {
|
|
|
45
26
|
setChannelRuntime?: (runtime: openclaw_plugin_sdk.PluginRuntime) => void;
|
|
46
27
|
};
|
|
47
28
|
|
|
48
|
-
export {
|
|
29
|
+
export { LingyaoConfig, plugin as default, getDefaultConfig, initializeLingyaoRuntime, lingyaoPlugin, validateConfig };
|
package/dist/index.js
CHANGED
|
@@ -63,6 +63,99 @@ function adaptPluginRuntime(pr) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// src/config-schema.ts
|
|
67
|
+
import { z } from "zod";
|
|
68
|
+
var lingyaoDmPolicySchema = z.enum(["pairing", "allowlist", "open"]);
|
|
69
|
+
var allowFromSchema = z.array(z.string()).optional();
|
|
70
|
+
var lingyaoAccountConfigSchema = z.object({
|
|
71
|
+
enabled: z.boolean().optional(),
|
|
72
|
+
dmPolicy: lingyaoDmPolicySchema.optional(),
|
|
73
|
+
allowFrom: allowFromSchema,
|
|
74
|
+
websocketHeartbeatIntervalMs: z.number().int().min(5e3).max(12e4).optional()
|
|
75
|
+
});
|
|
76
|
+
var lingyaoAccountConfigLooseSchema = z.object({
|
|
77
|
+
enabled: z.boolean().optional(),
|
|
78
|
+
dmPolicy: z.unknown().optional(),
|
|
79
|
+
allowFrom: z.array(z.string()).optional(),
|
|
80
|
+
gatewayId: z.string().optional(),
|
|
81
|
+
websocketHeartbeatIntervalMs: z.number().optional(),
|
|
82
|
+
maxOfflineMessages: z.number().optional(),
|
|
83
|
+
tokenExpiryDays: z.number().optional()
|
|
84
|
+
}).passthrough();
|
|
85
|
+
var lingyaoConfigSchema = z.object({
|
|
86
|
+
enabled: z.boolean().default(true),
|
|
87
|
+
dmPolicy: lingyaoDmPolicySchema.optional().default("pairing"),
|
|
88
|
+
allowFrom: allowFromSchema.default([]),
|
|
89
|
+
websocketHeartbeatIntervalMs: z.number().int().min(5e3).max(12e4).optional(),
|
|
90
|
+
defaultAccount: z.string().min(1).optional(),
|
|
91
|
+
accounts: z.record(lingyaoAccountConfigSchema).optional()
|
|
92
|
+
});
|
|
93
|
+
var lingyaoChannelConfigSchema = {
|
|
94
|
+
schema: {
|
|
95
|
+
type: "object",
|
|
96
|
+
additionalProperties: false,
|
|
97
|
+
properties: {
|
|
98
|
+
enabled: {
|
|
99
|
+
type: "boolean",
|
|
100
|
+
default: true
|
|
101
|
+
},
|
|
102
|
+
dmPolicy: {
|
|
103
|
+
type: "string",
|
|
104
|
+
enum: ["pairing", "allowlist", "open"],
|
|
105
|
+
default: "pairing"
|
|
106
|
+
},
|
|
107
|
+
allowFrom: {
|
|
108
|
+
type: "array",
|
|
109
|
+
items: { type: "string" },
|
|
110
|
+
default: []
|
|
111
|
+
},
|
|
112
|
+
defaultAccount: {
|
|
113
|
+
type: "string"
|
|
114
|
+
},
|
|
115
|
+
websocketHeartbeatIntervalMs: {
|
|
116
|
+
type: "integer",
|
|
117
|
+
minimum: 5e3,
|
|
118
|
+
maximum: 12e4,
|
|
119
|
+
description: "WebSocket gateway_heartbeat interval in ms (default: server register response). Use 5000\u201355000 for typical relay timeout."
|
|
120
|
+
},
|
|
121
|
+
accounts: {
|
|
122
|
+
type: "object",
|
|
123
|
+
additionalProperties: false,
|
|
124
|
+
properties: {
|
|
125
|
+
enabled: {
|
|
126
|
+
type: "boolean",
|
|
127
|
+
default: true
|
|
128
|
+
},
|
|
129
|
+
dmPolicy: {
|
|
130
|
+
type: "string",
|
|
131
|
+
enum: ["pairing", "allowlist", "open"]
|
|
132
|
+
},
|
|
133
|
+
allowFrom: {
|
|
134
|
+
type: "array",
|
|
135
|
+
items: { type: "string" }
|
|
136
|
+
},
|
|
137
|
+
websocketHeartbeatIntervalMs: {
|
|
138
|
+
type: "integer",
|
|
139
|
+
minimum: 5e3,
|
|
140
|
+
maximum: 12e4
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
default: {}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
function validateConfig(config) {
|
|
149
|
+
return lingyaoConfigSchema.parse(config);
|
|
150
|
+
}
|
|
151
|
+
function getDefaultConfig() {
|
|
152
|
+
return {
|
|
153
|
+
enabled: true,
|
|
154
|
+
dmPolicy: "pairing",
|
|
155
|
+
allowFrom: []
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
66
159
|
// src/adapters/config.ts
|
|
67
160
|
function normalizeDmPolicy(raw) {
|
|
68
161
|
switch (raw) {
|
|
@@ -128,13 +221,15 @@ function createConfigAdapter() {
|
|
|
128
221
|
if (!accountConfig) {
|
|
129
222
|
throw new Error(`Account "${resolvedId}" not found`);
|
|
130
223
|
}
|
|
224
|
+
const parsed = lingyaoAccountConfigLooseSchema.safeParse(accountConfig);
|
|
225
|
+
const ac = parsed.success ? parsed.data : {};
|
|
131
226
|
return {
|
|
132
227
|
id: resolvedId,
|
|
133
228
|
accountId: resolvedId,
|
|
134
|
-
enabled:
|
|
135
|
-
dmPolicy: normalizeDmPolicy(
|
|
136
|
-
allowFrom:
|
|
137
|
-
gatewayId:
|
|
229
|
+
enabled: ac.enabled !== false,
|
|
230
|
+
dmPolicy: normalizeDmPolicy(ac.dmPolicy),
|
|
231
|
+
allowFrom: Array.isArray(ac.allowFrom) ? ac.allowFrom : [],
|
|
232
|
+
gatewayId: typeof ac.gatewayId === "string" ? ac.gatewayId : void 0,
|
|
138
233
|
rawConfig: accountConfig
|
|
139
234
|
};
|
|
140
235
|
},
|
|
@@ -764,6 +859,8 @@ var ServerHttpClient = class {
|
|
|
764
859
|
|
|
765
860
|
// src/websocket-client.ts
|
|
766
861
|
import WebSocket from "ws";
|
|
862
|
+
var LINGYAO_DEFAULT_MAX_MESSAGE_BYTES = 1048576;
|
|
863
|
+
var LINGYAO_DEFAULT_MAX_INCOMING_WS_BYTES = 2048576;
|
|
767
864
|
function isWebsocketUpgradeNotFoundError(message) {
|
|
768
865
|
return /Unexpected server response:\s*404/i.test(message) || /\b404\b/.test(message);
|
|
769
866
|
}
|
|
@@ -790,6 +887,8 @@ var LingyaoWSClient = class {
|
|
|
790
887
|
reconnectTimer = null;
|
|
791
888
|
/** When set, close handler will not schedule reconnect (e.g. HTTP 404 on upgrade). */
|
|
792
889
|
suppressReconnect = false;
|
|
890
|
+
/** 自上次成功 `open` 以来已调度的重连次数(用于退避与上限) */
|
|
891
|
+
reconnectAttempt = 0;
|
|
793
892
|
messageHandlers = /* @__PURE__ */ new Map();
|
|
794
893
|
logger;
|
|
795
894
|
constructor(runtime, config) {
|
|
@@ -853,6 +952,7 @@ var LingyaoWSClient = class {
|
|
|
853
952
|
*/
|
|
854
953
|
handleOpen() {
|
|
855
954
|
this.state = "connected";
|
|
955
|
+
this.reconnectAttempt = 0;
|
|
856
956
|
this.connectionId = this.generateConnectionId();
|
|
857
957
|
this.logger.info("WebSocket connected to Lingyao server", {
|
|
858
958
|
connectionId: this.connectionId
|
|
@@ -873,6 +973,14 @@ var LingyaoWSClient = class {
|
|
|
873
973
|
*/
|
|
874
974
|
async handleMessage(data) {
|
|
875
975
|
try {
|
|
976
|
+
const maxBytes = this.config.maxIncomingMessageBytes ?? LINGYAO_DEFAULT_MAX_INCOMING_WS_BYTES;
|
|
977
|
+
if (data.length > maxBytes) {
|
|
978
|
+
this.logger.warn("Dropped oversize WebSocket frame", {
|
|
979
|
+
bytes: data.length,
|
|
980
|
+
maxBytes
|
|
981
|
+
});
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
876
984
|
const message = JSON.parse(data.toString());
|
|
877
985
|
const rawType = String(message.type);
|
|
878
986
|
const handlerKey = normalizeIncomingGatewayMessageType(rawType);
|
|
@@ -941,8 +1049,7 @@ var LingyaoWSClient = class {
|
|
|
941
1049
|
capabilities: {
|
|
942
1050
|
websocket: true,
|
|
943
1051
|
compression: false,
|
|
944
|
-
maxMessageSize:
|
|
945
|
-
// 1MB
|
|
1052
|
+
maxMessageSize: LINGYAO_DEFAULT_MAX_MESSAGE_BYTES
|
|
946
1053
|
}
|
|
947
1054
|
}
|
|
948
1055
|
};
|
|
@@ -1006,11 +1113,33 @@ var LingyaoWSClient = class {
|
|
|
1006
1113
|
if (this.reconnectTimer) {
|
|
1007
1114
|
return;
|
|
1008
1115
|
}
|
|
1009
|
-
|
|
1116
|
+
const maxAttempts = this.config.reconnectMaxAttempts ?? 0;
|
|
1117
|
+
if (maxAttempts > 0 && this.reconnectAttempt >= maxAttempts) {
|
|
1118
|
+
this.logger.error("Reconnect aborted: max attempts reached", {
|
|
1119
|
+
attempts: this.reconnectAttempt,
|
|
1120
|
+
maxAttempts
|
|
1121
|
+
});
|
|
1122
|
+
this.emitEvent({
|
|
1123
|
+
type: "reconnect_aborted",
|
|
1124
|
+
reason: "max_attempts",
|
|
1125
|
+
attempts: this.reconnectAttempt
|
|
1126
|
+
});
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const base = this.config.reconnectInterval;
|
|
1130
|
+
const mult = this.config.reconnectBackoffMultiplier ?? 2;
|
|
1131
|
+
const cap = this.config.reconnectMaxDelayMs ?? 6e4;
|
|
1132
|
+
const pow = Math.pow(mult, this.reconnectAttempt);
|
|
1133
|
+
const delay = Math.min(base * pow, cap);
|
|
1134
|
+
this.logger.info(`Scheduling reconnect`, {
|
|
1135
|
+
delayMs: delay,
|
|
1136
|
+
attempt: this.reconnectAttempt
|
|
1137
|
+
});
|
|
1138
|
+
this.reconnectAttempt += 1;
|
|
1010
1139
|
this.reconnectTimer = setTimeout(() => {
|
|
1011
1140
|
this.reconnectTimer = null;
|
|
1012
1141
|
this.connect();
|
|
1013
|
-
},
|
|
1142
|
+
}, delay);
|
|
1014
1143
|
}
|
|
1015
1144
|
/**
|
|
1016
1145
|
* 发送消息到服务器
|
|
@@ -1091,9 +1220,9 @@ var LingyaoWSClient = class {
|
|
|
1091
1220
|
if (this.config.eventHandler) {
|
|
1092
1221
|
this.config.eventHandler({
|
|
1093
1222
|
type: "pairing_completed",
|
|
1094
|
-
deviceId: payload?.deviceId,
|
|
1095
|
-
deviceInfo: payload?.deviceInfo,
|
|
1096
|
-
sessionId: payload?.sessionId
|
|
1223
|
+
deviceId: payload?.deviceId ?? "",
|
|
1224
|
+
deviceInfo: payload?.deviceInfo ?? { name: "", platform: "", version: "" },
|
|
1225
|
+
sessionId: payload?.sessionId ?? ""
|
|
1097
1226
|
});
|
|
1098
1227
|
}
|
|
1099
1228
|
}
|
|
@@ -1183,10 +1312,13 @@ var LingyaoWSClient = class {
|
|
|
1183
1312
|
// src/accounts.ts
|
|
1184
1313
|
var STORAGE_KEY_ACCOUNTS = "lingyao:accounts";
|
|
1185
1314
|
var STORAGE_KEY_PENDING_PAIRINGS = "lingyao:pending_pairings";
|
|
1186
|
-
var AccountManager = class {
|
|
1315
|
+
var AccountManager = class _AccountManager {
|
|
1187
1316
|
runtime;
|
|
1188
1317
|
accounts = /* @__PURE__ */ new Map();
|
|
1189
1318
|
pendingPairings = /* @__PURE__ */ new Map();
|
|
1319
|
+
/** Debounced flush for high-frequency updates (e.g. heartbeat lastSeen). */
|
|
1320
|
+
accountsSaveTimer = null;
|
|
1321
|
+
static ACCOUNTS_SAVE_DEBOUNCE_MS = 3e3;
|
|
1190
1322
|
constructor(runtime) {
|
|
1191
1323
|
this.runtime = runtime;
|
|
1192
1324
|
}
|
|
@@ -1309,7 +1441,31 @@ var AccountManager = class {
|
|
|
1309
1441
|
const account = this.accounts.get(deviceId);
|
|
1310
1442
|
if (account) {
|
|
1311
1443
|
account.lastSeenAt = Date.now();
|
|
1312
|
-
|
|
1444
|
+
this.scheduleDebouncedAccountsSave();
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Persist any debounced account state immediately (e.g. before process/account shutdown).
|
|
1449
|
+
*/
|
|
1450
|
+
async flushPendingAccountsSave() {
|
|
1451
|
+
if (this.accountsSaveTimer) {
|
|
1452
|
+
clearTimeout(this.accountsSaveTimer);
|
|
1453
|
+
this.accountsSaveTimer = null;
|
|
1454
|
+
}
|
|
1455
|
+
await this.persistAccounts();
|
|
1456
|
+
}
|
|
1457
|
+
scheduleDebouncedAccountsSave() {
|
|
1458
|
+
if (this.accountsSaveTimer) {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
this.accountsSaveTimer = setTimeout(() => {
|
|
1462
|
+
this.accountsSaveTimer = null;
|
|
1463
|
+
void this.persistAccounts().catch(
|
|
1464
|
+
(e) => this.runtime.logger.error("Failed to save accounts (debounced)", e)
|
|
1465
|
+
);
|
|
1466
|
+
}, _AccountManager.ACCOUNTS_SAVE_DEBOUNCE_MS);
|
|
1467
|
+
if (this.accountsSaveTimer.unref) {
|
|
1468
|
+
this.accountsSaveTimer.unref();
|
|
1313
1469
|
}
|
|
1314
1470
|
}
|
|
1315
1471
|
/**
|
|
@@ -1394,9 +1550,16 @@ var AccountManager = class {
|
|
|
1394
1550
|
}
|
|
1395
1551
|
}
|
|
1396
1552
|
/**
|
|
1397
|
-
* Save accounts to storage
|
|
1553
|
+
* Save accounts to storage (immediate; clears any pending debounced save).
|
|
1398
1554
|
*/
|
|
1399
1555
|
async saveAccounts() {
|
|
1556
|
+
if (this.accountsSaveTimer) {
|
|
1557
|
+
clearTimeout(this.accountsSaveTimer);
|
|
1558
|
+
this.accountsSaveTimer = null;
|
|
1559
|
+
}
|
|
1560
|
+
await this.persistAccounts();
|
|
1561
|
+
}
|
|
1562
|
+
async persistAccounts() {
|
|
1400
1563
|
const accounts = Array.from(this.accounts.values());
|
|
1401
1564
|
await this.runtime.storage.set(STORAGE_KEY_ACCOUNTS, accounts);
|
|
1402
1565
|
}
|
|
@@ -1602,6 +1765,7 @@ var Probe = class {
|
|
|
1602
1765
|
};
|
|
1603
1766
|
|
|
1604
1767
|
// src/metrics.ts
|
|
1768
|
+
var MAX_HISTOGRAM_SAMPLES_PER_NAME = 1e4;
|
|
1605
1769
|
var MetricsManager = class {
|
|
1606
1770
|
metrics = /* @__PURE__ */ new Map();
|
|
1607
1771
|
counters = /* @__PURE__ */ new Map();
|
|
@@ -1720,6 +1884,9 @@ var MetricsManager = class {
|
|
|
1720
1884
|
this.histograms.set(name, values);
|
|
1721
1885
|
}
|
|
1722
1886
|
values.push(value);
|
|
1887
|
+
if (values.length > MAX_HISTOGRAM_SAMPLES_PER_NAME) {
|
|
1888
|
+
values.splice(0, values.length - MAX_HISTOGRAM_SAMPLES_PER_NAME);
|
|
1889
|
+
}
|
|
1723
1890
|
this.recordMetric({
|
|
1724
1891
|
name,
|
|
1725
1892
|
type: "histogram" /* HISTOGRAM */,
|
|
@@ -1940,6 +2107,7 @@ var Monitor = class {
|
|
|
1940
2107
|
};
|
|
1941
2108
|
|
|
1942
2109
|
// src/errors.ts
|
|
2110
|
+
var MAX_LAST_ERROR_ENTRIES = 128;
|
|
1943
2111
|
var LingyaoError = class extends Error {
|
|
1944
2112
|
code;
|
|
1945
2113
|
severity;
|
|
@@ -2058,6 +2226,11 @@ var ErrorHandler = class {
|
|
|
2058
2226
|
error,
|
|
2059
2227
|
timestamp
|
|
2060
2228
|
});
|
|
2229
|
+
while (this.lastErrors.size > MAX_LAST_ERROR_ENTRIES) {
|
|
2230
|
+
const oldest = this.lastErrors.keys().next().value;
|
|
2231
|
+
if (oldest === void 0) break;
|
|
2232
|
+
this.lastErrors.delete(oldest);
|
|
2233
|
+
}
|
|
2061
2234
|
}
|
|
2062
2235
|
/**
|
|
2063
2236
|
* 检查错误阈值
|
|
@@ -2230,6 +2403,38 @@ var ErrorHandler = class {
|
|
|
2230
2403
|
}
|
|
2231
2404
|
};
|
|
2232
2405
|
|
|
2406
|
+
// src/notification-rate-limit.ts
|
|
2407
|
+
var NotificationRateLimiter = class {
|
|
2408
|
+
constructor(refillPerSecond, burst) {
|
|
2409
|
+
this.refillPerSecond = refillPerSecond;
|
|
2410
|
+
this.burst = burst;
|
|
2411
|
+
}
|
|
2412
|
+
buckets = /* @__PURE__ */ new Map();
|
|
2413
|
+
/**
|
|
2414
|
+
* Attempt to consume one send slot for `accountId`. Returns false if rate-limited.
|
|
2415
|
+
*/
|
|
2416
|
+
tryConsume(accountId) {
|
|
2417
|
+
const now = Date.now();
|
|
2418
|
+
let b = this.buckets.get(accountId);
|
|
2419
|
+
if (!b) {
|
|
2420
|
+
b = { tokens: this.burst, lastUpdate: now };
|
|
2421
|
+
this.buckets.set(accountId, b);
|
|
2422
|
+
}
|
|
2423
|
+
const elapsedSec = (now - b.lastUpdate) / 1e3;
|
|
2424
|
+
if (elapsedSec > 0) {
|
|
2425
|
+
b.tokens = Math.min(this.burst, b.tokens + elapsedSec * this.refillPerSecond);
|
|
2426
|
+
b.lastUpdate = now;
|
|
2427
|
+
}
|
|
2428
|
+
if (b.tokens >= 1) {
|
|
2429
|
+
b.tokens -= 1;
|
|
2430
|
+
return true;
|
|
2431
|
+
}
|
|
2432
|
+
return false;
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
var DEFAULT_NOTIFY_REFILL_PER_SEC = 15;
|
|
2436
|
+
var DEFAULT_NOTIFY_BURST = 25;
|
|
2437
|
+
|
|
2233
2438
|
// src/orchestrator.ts
|
|
2234
2439
|
function getMachineId() {
|
|
2235
2440
|
try {
|
|
@@ -2247,6 +2452,10 @@ function generateGatewayId(accountId) {
|
|
|
2247
2452
|
var MultiAccountOrchestrator = class {
|
|
2248
2453
|
runtime;
|
|
2249
2454
|
accounts = /* @__PURE__ */ new Map();
|
|
2455
|
+
notificationLimiter = new NotificationRateLimiter(
|
|
2456
|
+
DEFAULT_NOTIFY_REFILL_PER_SEC,
|
|
2457
|
+
DEFAULT_NOTIFY_BURST
|
|
2458
|
+
);
|
|
2250
2459
|
constructor(runtime) {
|
|
2251
2460
|
this.runtime = runtime;
|
|
2252
2461
|
}
|
|
@@ -2351,6 +2560,11 @@ var MultiAccountOrchestrator = class {
|
|
|
2351
2560
|
if (state.httpClient) {
|
|
2352
2561
|
state.httpClient.stopHeartbeat();
|
|
2353
2562
|
}
|
|
2563
|
+
try {
|
|
2564
|
+
await state.accountManager.flushPendingAccountsSave();
|
|
2565
|
+
} catch (e) {
|
|
2566
|
+
this.runtime.logger.error(`[${accountId}] Failed to flush account storage`, e);
|
|
2567
|
+
}
|
|
2354
2568
|
state.status = "stopped";
|
|
2355
2569
|
this.runtime.logger.info(`Account "${accountId}" stopped`);
|
|
2356
2570
|
}
|
|
@@ -2407,6 +2621,12 @@ var MultiAccountOrchestrator = class {
|
|
|
2407
2621
|
this.runtime.logger.warn(`[${accountId}] Cannot send notification: WS not connected`, { deviceId });
|
|
2408
2622
|
return false;
|
|
2409
2623
|
}
|
|
2624
|
+
if (!this.notificationLimiter.tryConsume(accountId)) {
|
|
2625
|
+
this.runtime.logger.warn(`[${accountId}] Notification rate limited (relay protection)`, {
|
|
2626
|
+
deviceId
|
|
2627
|
+
});
|
|
2628
|
+
return false;
|
|
2629
|
+
}
|
|
2410
2630
|
this.runtime.logger.info(`[${accountId}] Sending notification to device`, {
|
|
2411
2631
|
deviceId,
|
|
2412
2632
|
notificationType: notification.type
|
|
@@ -2439,7 +2659,7 @@ var MultiAccountOrchestrator = class {
|
|
|
2439
2659
|
const response = await state.httpClient.register({
|
|
2440
2660
|
websocket: true,
|
|
2441
2661
|
compression: false,
|
|
2442
|
-
maxMessageSize:
|
|
2662
|
+
maxMessageSize: LINGYAO_DEFAULT_MAX_MESSAGE_BYTES
|
|
2443
2663
|
});
|
|
2444
2664
|
this.runtime.logger.info(`Account "${state.accountId}": registered successfully`, {
|
|
2445
2665
|
expiresAt: new Date(response.expiresAt).toISOString()
|
|
@@ -2466,17 +2686,14 @@ var MultiAccountOrchestrator = class {
|
|
|
2466
2686
|
deviceId,
|
|
2467
2687
|
messageType: msg.type
|
|
2468
2688
|
});
|
|
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 });
|
|
2689
|
+
if (msg.type === "sync_diary" || msg.type === "sync_memory") {
|
|
2690
|
+
const syncBody = msg;
|
|
2691
|
+
await this.handleSyncMessage(state, deviceId, syncBody);
|
|
2692
|
+
} else if (msg.type === "heartbeat") {
|
|
2693
|
+
await state.accountManager.updateLastSeen(deviceId);
|
|
2694
|
+
state.monitor.recordEvent("heartbeat_received" /* HEARTBEAT_RECEIVED */, { deviceId });
|
|
2695
|
+
} else {
|
|
2696
|
+
this.runtime.logger.warn(`[${state.accountId}] Unknown message type`, { type: msg.type });
|
|
2480
2697
|
}
|
|
2481
2698
|
} catch (error) {
|
|
2482
2699
|
this.runtime.logger.error(`[${state.accountId}] Error handling App message`, error);
|
|
@@ -2609,12 +2826,20 @@ var MultiAccountOrchestrator = class {
|
|
|
2609
2826
|
break;
|
|
2610
2827
|
case "fatal_handshake":
|
|
2611
2828
|
if (event.reason === "http_404") {
|
|
2612
|
-
|
|
2829
|
+
this.handleWsHandshake404(state).catch((err) => {
|
|
2830
|
+
this.runtime.logger.error(`[${state.accountId}] WS 404 recovery (async) failed`, err);
|
|
2831
|
+
});
|
|
2613
2832
|
}
|
|
2614
2833
|
break;
|
|
2615
2834
|
case "pairing_completed":
|
|
2616
2835
|
this.handlePairingCompleted(state, event);
|
|
2617
2836
|
break;
|
|
2837
|
+
case "reconnect_aborted":
|
|
2838
|
+
this.runtime.logger.error(`[${state.accountId}] WebSocket reconnect aborted`, {
|
|
2839
|
+
reason: event.reason,
|
|
2840
|
+
attempts: event.attempts
|
|
2841
|
+
});
|
|
2842
|
+
break;
|
|
2618
2843
|
}
|
|
2619
2844
|
};
|
|
2620
2845
|
}
|
|
@@ -2657,7 +2882,7 @@ var MultiAccountOrchestrator = class {
|
|
|
2657
2882
|
const response = await httpClient.register({
|
|
2658
2883
|
websocket: true,
|
|
2659
2884
|
compression: false,
|
|
2660
|
-
maxMessageSize:
|
|
2885
|
+
maxMessageSize: LINGYAO_DEFAULT_MAX_MESSAGE_BYTES
|
|
2661
2886
|
});
|
|
2662
2887
|
wsClient.updateToken(response.gatewayToken);
|
|
2663
2888
|
await wsClient.connect();
|
|
@@ -2686,7 +2911,7 @@ var MultiAccountOrchestrator = class {
|
|
|
2686
2911
|
const response = await state.httpClient.register({
|
|
2687
2912
|
websocket: true,
|
|
2688
2913
|
compression: false,
|
|
2689
|
-
maxMessageSize:
|
|
2914
|
+
maxMessageSize: LINGYAO_DEFAULT_MAX_MESSAGE_BYTES
|
|
2690
2915
|
});
|
|
2691
2916
|
this.runtime.logger.info(`[${state.accountId}] Obtained new token. Reconnecting WS...`);
|
|
2692
2917
|
state.wsClient.updateToken(response.gatewayToken);
|
|
@@ -2698,90 +2923,6 @@ var MultiAccountOrchestrator = class {
|
|
|
2698
2923
|
}
|
|
2699
2924
|
};
|
|
2700
2925
|
|
|
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
2926
|
// src/api.ts
|
|
2786
2927
|
var orchestrator = null;
|
|
2787
2928
|
function getOrchestrator() {
|