lobster-roundtable 3.0.4 → 3.0.5
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/main.js +45 -46
- package/openclaw.plugin.json +2 -2
- package/package.json +2 -2
package/main.js
CHANGED
|
@@ -60,7 +60,7 @@ try {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const CHANNEL_ID = "lobster-roundtable";
|
|
63
|
-
const PLUGIN_VERSION = "3.0.
|
|
63
|
+
const PLUGIN_VERSION = "3.0.5";
|
|
64
64
|
const ENABLE_OPENCLAW_CONFIG_SYNC = isOpenClawConfigSyncEnabled();
|
|
65
65
|
const OPENCLAW_CONFIG_ALLOWED_KEYS = new Set(["url", "token", "ownerToken", "name", "persona", "maxTokens"]);
|
|
66
66
|
|
|
@@ -227,21 +227,6 @@ async function requestAutoRegister(httpBase, payload) {
|
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
async function requestTokenInfo(httpBase, token) {
|
|
231
|
-
const safeToken = String(token || "").trim();
|
|
232
|
-
if (!safeToken) return null;
|
|
233
|
-
try {
|
|
234
|
-
const res = await httpRequest(`${httpBase}/api/tokens/${encodeURIComponent(safeToken)}`, {
|
|
235
|
-
timeout: 12000,
|
|
236
|
-
});
|
|
237
|
-
const parsed = JSON.parse(String(res.data || "{}"));
|
|
238
|
-
if (parsed && typeof parsed === "object") return parsed;
|
|
239
|
-
return null;
|
|
240
|
-
} catch {
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
230
|
function upsertOpenClawPluginConfig(ocHome, wsUrl, updates, logger) {
|
|
246
231
|
const ocConfigPath = pathModule.join(ocHome, "openclaw.json");
|
|
247
232
|
if (!fsModule.existsSync(ocConfigPath)) return;
|
|
@@ -347,43 +332,25 @@ module.exports = async function initRoundtable(api, core, hasRuntimeAPI) {
|
|
|
347
332
|
writeTextSafe(instanceIdFile, instanceId);
|
|
348
333
|
|
|
349
334
|
const cachedToken = String(readTextSafe(tokenCacheFile) || "").trim();
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
} else {
|
|
357
|
-
try {
|
|
358
|
-
const info = await requestTokenInfo(httpBase, token);
|
|
359
|
-
if (info?.token && String(info.token).trim() === token) {
|
|
360
|
-
useConfiguredToken = true;
|
|
361
|
-
decisionReason = "配置 token 在服务端存在(视为新安装)";
|
|
362
|
-
}
|
|
363
|
-
} catch {
|
|
364
|
-
// 服务端不可达时保持缓存优先,避免旧配置回滚
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (useConfiguredToken) {
|
|
369
|
-
api.logger.warn(`[roundtable] 🔁 检测到新 token,采用配置 token(${decisionReason})`);
|
|
370
|
-
writeTextSafe(tokenCacheFile, token);
|
|
371
|
-
} else {
|
|
372
|
-
api.logger.warn("[roundtable] ⚠️ 检测到配置 token 与本地缓存不一致,优先使用缓存 token");
|
|
373
|
-
token = cachedToken;
|
|
374
|
-
tokenSource = 'cache';
|
|
335
|
+
if (token) {
|
|
336
|
+
if (cachedToken && token !== cachedToken) {
|
|
337
|
+
api.logger.warn("[roundtable] 🔁 检测到配置 token 与本地缓存不一致,按配置 token 启动并覆盖缓存");
|
|
338
|
+
} else if (cachedToken && token === cachedToken) {
|
|
339
|
+
tokenSource = 'config+cache';
|
|
340
|
+
api.logger.info("[roundtable] 📦 命中本地 token 缓存");
|
|
375
341
|
}
|
|
376
|
-
|
|
342
|
+
writeTextSafe(tokenCacheFile, token);
|
|
343
|
+
} else if (cachedToken) {
|
|
377
344
|
api.logger.info("[roundtable] 📦 从本地缓存加载 token");
|
|
378
345
|
token = cachedToken;
|
|
379
346
|
tokenSource = 'cache';
|
|
380
|
-
} else if (cachedToken && token === cachedToken) {
|
|
381
|
-
api.logger.info("[roundtable] 📦 命中本地 token 缓存");
|
|
382
|
-
tokenSource = 'config+cache';
|
|
383
347
|
}
|
|
384
348
|
|
|
385
349
|
const cachedOwnerToken = normalizeIdentityId(readTextSafe(ownerTokenCacheFile), 128);
|
|
386
|
-
if (
|
|
350
|
+
if (ownerToken && cachedOwnerToken && ownerToken !== cachedOwnerToken) {
|
|
351
|
+
api.logger.warn("[roundtable] 🔁 检测到配置 ownerToken 与缓存不一致,按配置 ownerToken 覆盖缓存");
|
|
352
|
+
writeTextSafe(ownerTokenCacheFile, ownerToken);
|
|
353
|
+
} else if (!ownerToken && cachedOwnerToken) {
|
|
387
354
|
ownerToken = cachedOwnerToken;
|
|
388
355
|
api.logger.info("[roundtable] 🔗 从本地缓存恢复 ownerToken");
|
|
389
356
|
}
|
|
@@ -487,6 +454,8 @@ function startBot(api, core, cfg, wsUrl, token, persona, maxTokens, tokenCacheFi
|
|
|
487
454
|
const sessionId = normalizeIdentityId(identityCtx.sessionId, 120) || cryptoModule.randomBytes(12).toString("hex");
|
|
488
455
|
const tokenSource = identityCtx.tokenSource || 'unknown';
|
|
489
456
|
let recoveringToken = false;
|
|
457
|
+
let ackReceivedForSocket = false;
|
|
458
|
+
let awaitingAckTimer = null;
|
|
490
459
|
|
|
491
460
|
// Token 冲突熔断器:防止无限重试导致日志爆炸和 CPU 空转
|
|
492
461
|
const conflictBreaker = {
|
|
@@ -527,6 +496,29 @@ function startBot(api, core, cfg, wsUrl, token, persona, maxTokens, tokenCacheFi
|
|
|
527
496
|
if (ownerToken) writeTextSafe(ownerTokenCacheFile, ownerToken);
|
|
528
497
|
}
|
|
529
498
|
|
|
499
|
+
function clearAckTimer() {
|
|
500
|
+
if (awaitingAckTimer) {
|
|
501
|
+
clearTimeout(awaitingAckTimer);
|
|
502
|
+
awaitingAckTimer = null;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function armAckTimer() {
|
|
507
|
+
clearAckTimer();
|
|
508
|
+
if (!token || !ws || ws.readyState !== WebSocket.OPEN) return;
|
|
509
|
+
awaitingAckTimer = setTimeout(() => {
|
|
510
|
+
if (stopping || !ws || ws.readyState !== WebSocket.OPEN || ackReceivedForSocket) return;
|
|
511
|
+
api.logger.warn("[roundtable] ⚠️ 已连接 WS 但未收到 bot_ack,主动重连");
|
|
512
|
+
reportDiag("bot_ack_timeout", {
|
|
513
|
+
level: "warn",
|
|
514
|
+
message: "websocket open but bot_ack not received in time",
|
|
515
|
+
detail: { timeoutMs: 12000, tokenSource },
|
|
516
|
+
}, 0);
|
|
517
|
+
try { ws.close(4000, "bot-ack-timeout"); } catch { }
|
|
518
|
+
}, 12000);
|
|
519
|
+
if (awaitingAckTimer.unref) awaitingAckTimer.unref();
|
|
520
|
+
}
|
|
521
|
+
|
|
530
522
|
function nextReconnectDelayMs() {
|
|
531
523
|
const base = Math.min(30000, Math.round(2500 * Math.pow(1.6, reconnectAttempts)));
|
|
532
524
|
reconnectAttempts += 1;
|
|
@@ -753,6 +745,8 @@ function startBot(api, core, cfg, wsUrl, token, persona, maxTokens, tokenCacheFi
|
|
|
753
745
|
|
|
754
746
|
ws.onopen = () => {
|
|
755
747
|
reconnectAttempts = 0;
|
|
748
|
+
ackReceivedForSocket = false;
|
|
749
|
+
clearAckTimer();
|
|
756
750
|
reportDiag("ws_open", {
|
|
757
751
|
message: token ? "connected with token" : "connected in observe mode",
|
|
758
752
|
detail: { instanceId, sessionId },
|
|
@@ -781,6 +775,7 @@ function startBot(api, core, cfg, wsUrl, token, persona, maxTokens, tokenCacheFi
|
|
|
781
775
|
pluginVersion: PLUGIN_VERSION,
|
|
782
776
|
tokenSource: tokenSource || 'unknown',
|
|
783
777
|
});
|
|
778
|
+
armAckTimer();
|
|
784
779
|
api.logger.info(`[roundtable] 🔗 bot_connect 已发送 (token=${token.slice(0, 8)}... source=${tokenSource || 'unknown'})`);
|
|
785
780
|
};
|
|
786
781
|
|
|
@@ -805,6 +800,7 @@ function startBot(api, core, cfg, wsUrl, token, persona, maxTokens, tokenCacheFi
|
|
|
805
800
|
};
|
|
806
801
|
|
|
807
802
|
ws.onclose = (evt) => {
|
|
803
|
+
clearAckTimer();
|
|
808
804
|
// 断连后立即清理本地 room 状态,避免重连后状态错乱
|
|
809
805
|
currentRoomId = null;
|
|
810
806
|
currentRoomMode = null;
|
|
@@ -1600,6 +1596,8 @@ function startBot(api, core, cfg, wsUrl, token, persona, maxTokens, tokenCacheFi
|
|
|
1600
1596
|
|
|
1601
1597
|
switch (msg.type) {
|
|
1602
1598
|
case "bot_ack":
|
|
1599
|
+
ackReceivedForSocket = true;
|
|
1600
|
+
clearAckTimer();
|
|
1603
1601
|
// 在大厅待机中
|
|
1604
1602
|
if (msg.status === 'lobby') {
|
|
1605
1603
|
myName = msg.name || myName;
|
|
@@ -1982,6 +1980,7 @@ function startBot(api, core, cfg, wsUrl, token, persona, maxTokens, tokenCacheFi
|
|
|
1982
1980
|
if (heartbeatTimer) { clearInterval(heartbeatTimer); }
|
|
1983
1981
|
// 停止重连
|
|
1984
1982
|
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
1983
|
+
clearAckTimer();
|
|
1985
1984
|
// 关闭 WS
|
|
1986
1985
|
if (ws) {
|
|
1987
1986
|
try { ws.close(1000, "shutdown"); } catch { }
|
package/openclaw.plugin.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"lobster-roundtable"
|
|
6
6
|
],
|
|
7
7
|
"description": "Connect OpenClaw to the Lobster Roundtable service.",
|
|
8
|
-
"version": "3.0.
|
|
8
|
+
"version": "3.0.5",
|
|
9
9
|
"configSchema": {
|
|
10
10
|
"type": "object",
|
|
11
11
|
"additionalProperties": false,
|
|
@@ -67,4 +67,4 @@
|
|
|
67
67
|
"placeholder": "龙虾"
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
}
|
|
70
|
+
}
|
package/package.json
CHANGED