@lingyao037/openclaw-lingyao-cli 1.3.5 → 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 CHANGED
@@ -1,4 +1,4 @@
1
- import { L as LingyaoRuntime, a as LingyaoAccount, D as DeviceToken, b as DeviceInfo, H as HealthStatus } from './types-BZMU9mea.js';
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-CS7AsRlS.js';
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-BZMU9mea.js';
5
- export { A as AckRequest, b as DeviceInfo, D as DeviceToken, d as DiarySyncPayload, F as FailedEntry, H as HealthStatus, e as LINGYAO_SERVER_URL, a as LingyaoAccount, f as LingyaoAccountConfig, g as LingyaoMessage, L as LingyaoRuntime, M as MemorySyncPayload, h as MessageType, N as NotifyAction, i as NotifyPayload, j as NotifyRequest, P as PairingCode, k as PairingConfirmRequest, l as PairingConfirmResponse, m as PollRequest, n as PollResponse, Q as QueuedMessage, S as SyncRequest, o as SyncResponse, T as TokenRefreshRequest, p as TokenRefreshResponse, W as WebSocketConnection, q as getLingyaoGatewayWsUrl } from './types-BZMU9mea.js';
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 { type AgentMessage, LingyaoConfig, plugin as default, getDefaultConfig, initializeLingyaoRuntime, lingyaoPlugin, validateConfig };
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: accountConfig?.enabled !== false,
135
- dmPolicy: normalizeDmPolicy(accountConfig?.dmPolicy),
136
- allowFrom: accountConfig?.allowFrom ?? [],
137
- gatewayId: accountConfig?.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: 1048576
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
- this.logger.info(`Scheduling reconnect in ${this.config.reconnectInterval}ms`);
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
- }, this.config.reconnectInterval);
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
- await this.saveAccounts();
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
  }
@@ -2261,8 +2470,30 @@ var MultiAccountOrchestrator = class {
2261
2470
  return;
2262
2471
  }
2263
2472
  this.runtime.logger.info(`Starting account "${accountId}"`);
2264
- const gatewayId = account.gatewayId ?? generateGatewayId(accountId);
2265
2473
  const storagePrefix = `lingyao:${accountId}`;
2474
+ const gatewayIdKey = `${storagePrefix}:persistedGatewayId`;
2475
+ let gatewayId = account.gatewayId;
2476
+ if (!gatewayId) {
2477
+ try {
2478
+ const stored = await this.runtime.storage.get(gatewayIdKey);
2479
+ if (stored && typeof stored === "string") {
2480
+ gatewayId = stored;
2481
+ this.runtime.logger.info(`Using persisted gatewayId: ${gatewayId}`);
2482
+ }
2483
+ } catch (e) {
2484
+ this.runtime.logger.warn(`Failed to read persisted gatewayId`, e);
2485
+ }
2486
+ }
2487
+ if (!gatewayId) {
2488
+ gatewayId = generateGatewayId(accountId);
2489
+ this.runtime.logger.info(`Generated new gatewayId: ${gatewayId}`);
2490
+ try {
2491
+ await this.runtime.storage.set(gatewayIdKey, gatewayId);
2492
+ this.runtime.logger.info(`Persisted gatewayId: ${gatewayId}`);
2493
+ } catch (e) {
2494
+ this.runtime.logger.warn(`Failed to persist gatewayId`, e);
2495
+ }
2496
+ }
2266
2497
  const accountManager = new AccountManager(this.runtime);
2267
2498
  const probe = new Probe(this.runtime);
2268
2499
  const monitor = new Monitor(this.runtime);
@@ -2329,6 +2560,11 @@ var MultiAccountOrchestrator = class {
2329
2560
  if (state.httpClient) {
2330
2561
  state.httpClient.stopHeartbeat();
2331
2562
  }
2563
+ try {
2564
+ await state.accountManager.flushPendingAccountsSave();
2565
+ } catch (e) {
2566
+ this.runtime.logger.error(`[${accountId}] Failed to flush account storage`, e);
2567
+ }
2332
2568
  state.status = "stopped";
2333
2569
  this.runtime.logger.info(`Account "${accountId}" stopped`);
2334
2570
  }
@@ -2385,6 +2621,12 @@ var MultiAccountOrchestrator = class {
2385
2621
  this.runtime.logger.warn(`[${accountId}] Cannot send notification: WS not connected`, { deviceId });
2386
2622
  return false;
2387
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
+ }
2388
2630
  this.runtime.logger.info(`[${accountId}] Sending notification to device`, {
2389
2631
  deviceId,
2390
2632
  notificationType: notification.type
@@ -2417,7 +2659,7 @@ var MultiAccountOrchestrator = class {
2417
2659
  const response = await state.httpClient.register({
2418
2660
  websocket: true,
2419
2661
  compression: false,
2420
- maxMessageSize: 1048576
2662
+ maxMessageSize: LINGYAO_DEFAULT_MAX_MESSAGE_BYTES
2421
2663
  });
2422
2664
  this.runtime.logger.info(`Account "${state.accountId}": registered successfully`, {
2423
2665
  expiresAt: new Date(response.expiresAt).toISOString()
@@ -2444,17 +2686,14 @@ var MultiAccountOrchestrator = class {
2444
2686
  deviceId,
2445
2687
  messageType: msg.type
2446
2688
  });
2447
- switch (msg.type) {
2448
- case "sync_diary":
2449
- case "sync_memory":
2450
- await this.handleSyncMessage(state, deviceId, msg);
2451
- break;
2452
- case "heartbeat":
2453
- await state.accountManager.updateLastSeen(deviceId);
2454
- state.monitor.recordEvent("heartbeat_received" /* HEARTBEAT_RECEIVED */, { deviceId });
2455
- break;
2456
- default:
2457
- 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 });
2458
2697
  }
2459
2698
  } catch (error) {
2460
2699
  this.runtime.logger.error(`[${state.accountId}] Error handling App message`, error);
@@ -2587,12 +2826,20 @@ var MultiAccountOrchestrator = class {
2587
2826
  break;
2588
2827
  case "fatal_handshake":
2589
2828
  if (event.reason === "http_404") {
2590
- void this.handleWsHandshake404(state);
2829
+ this.handleWsHandshake404(state).catch((err) => {
2830
+ this.runtime.logger.error(`[${state.accountId}] WS 404 recovery (async) failed`, err);
2831
+ });
2591
2832
  }
2592
2833
  break;
2593
2834
  case "pairing_completed":
2594
2835
  this.handlePairingCompleted(state, event);
2595
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;
2596
2843
  }
2597
2844
  };
2598
2845
  }
@@ -2635,7 +2882,7 @@ var MultiAccountOrchestrator = class {
2635
2882
  const response = await httpClient.register({
2636
2883
  websocket: true,
2637
2884
  compression: false,
2638
- maxMessageSize: 1048576
2885
+ maxMessageSize: LINGYAO_DEFAULT_MAX_MESSAGE_BYTES
2639
2886
  });
2640
2887
  wsClient.updateToken(response.gatewayToken);
2641
2888
  await wsClient.connect();
@@ -2664,7 +2911,7 @@ var MultiAccountOrchestrator = class {
2664
2911
  const response = await state.httpClient.register({
2665
2912
  websocket: true,
2666
2913
  compression: false,
2667
- maxMessageSize: 1048576
2914
+ maxMessageSize: LINGYAO_DEFAULT_MAX_MESSAGE_BYTES
2668
2915
  });
2669
2916
  this.runtime.logger.info(`[${state.accountId}] Obtained new token. Reconnecting WS...`);
2670
2917
  state.wsClient.updateToken(response.gatewayToken);
@@ -2676,90 +2923,6 @@ var MultiAccountOrchestrator = class {
2676
2923
  }
2677
2924
  };
2678
2925
 
2679
- // src/config-schema.ts
2680
- import { z } from "zod";
2681
- var lingyaoDmPolicySchema = z.enum(["pairing", "allowlist", "open"]);
2682
- var allowFromSchema = z.array(z.string()).optional();
2683
- var lingyaoAccountConfigSchema = z.object({
2684
- enabled: z.boolean().optional(),
2685
- dmPolicy: lingyaoDmPolicySchema.optional(),
2686
- allowFrom: allowFromSchema,
2687
- websocketHeartbeatIntervalMs: z.number().int().min(5e3).max(12e4).optional()
2688
- });
2689
- var lingyaoConfigSchema = z.object({
2690
- enabled: z.boolean().default(true),
2691
- dmPolicy: lingyaoDmPolicySchema.optional().default("pairing"),
2692
- allowFrom: allowFromSchema.default([]),
2693
- websocketHeartbeatIntervalMs: z.number().int().min(5e3).max(12e4).optional(),
2694
- defaultAccount: z.string().min(1).optional(),
2695
- accounts: z.record(lingyaoAccountConfigSchema).optional()
2696
- });
2697
- var lingyaoChannelConfigSchema = {
2698
- schema: {
2699
- type: "object",
2700
- additionalProperties: false,
2701
- properties: {
2702
- enabled: {
2703
- type: "boolean",
2704
- default: true
2705
- },
2706
- dmPolicy: {
2707
- type: "string",
2708
- enum: ["pairing", "allowlist", "open"],
2709
- default: "pairing"
2710
- },
2711
- allowFrom: {
2712
- type: "array",
2713
- items: { type: "string" },
2714
- default: []
2715
- },
2716
- defaultAccount: {
2717
- type: "string"
2718
- },
2719
- websocketHeartbeatIntervalMs: {
2720
- type: "integer",
2721
- minimum: 5e3,
2722
- maximum: 12e4,
2723
- description: "WebSocket gateway_heartbeat interval in ms (default: server register response). Use 5000\u201355000 for typical relay timeout."
2724
- },
2725
- accounts: {
2726
- type: "object",
2727
- additionalProperties: false,
2728
- properties: {
2729
- enabled: {
2730
- type: "boolean",
2731
- default: true
2732
- },
2733
- dmPolicy: {
2734
- type: "string",
2735
- enum: ["pairing", "allowlist", "open"]
2736
- },
2737
- allowFrom: {
2738
- type: "array",
2739
- items: { type: "string" }
2740
- },
2741
- websocketHeartbeatIntervalMs: {
2742
- type: "integer",
2743
- minimum: 5e3,
2744
- maximum: 12e4
2745
- }
2746
- },
2747
- default: {}
2748
- }
2749
- }
2750
- }
2751
- };
2752
- function validateConfig(config) {
2753
- return lingyaoConfigSchema.parse(config);
2754
- }
2755
- function getDefaultConfig() {
2756
- return {
2757
- enabled: true,
2758
- dmPolicy: "pairing",
2759
- allowFrom: []
2760
- };
2761
- }
2762
-
2763
2926
  // src/api.ts
2764
2927
  var orchestrator = null;
2765
2928
  function getOrchestrator() {