cortico-world-astrbot 1.0.2 → 1.0.3
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/CHANGELOG.md +4 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/driver.ts +65 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
让 Cortico 的人格 Bot 进 QQ 群说话,同时把 QQ 账号与插件能力留在 AstrBot 手里。
|
|
6
6
|
|
|
7
|
-
**当前版本:1.0.
|
|
7
|
+
**当前版本:1.0.3**(更新内容见 [CHANGELOG.md](CHANGELOG.md))
|
|
8
8
|
|
|
9
9
|
项目地址:[as124da1231/cortico-world-astrbot](https://github.com/as124da1231/cortico-world-astrbot)。
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cortico-world-astrbot",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Cortico World: join QQ conversations through an AstrBot bridge, so the bot keeps AstrBot's plugins while a Cortico persona drives the talking",
|
|
6
6
|
"license": "MIT",
|
package/src/driver.ts
CHANGED
|
@@ -94,6 +94,10 @@ export interface BridgeDriverOptions {
|
|
|
94
94
|
reconnectMaxMs?: number;
|
|
95
95
|
/** 一次发送等确认的超时。 */
|
|
96
96
|
sendTimeoutMs?: number;
|
|
97
|
+
/** 心跳间隔;0 表示不做活性检测。 */
|
|
98
|
+
heartbeatMs?: number;
|
|
99
|
+
/** 超过这么久没有任何往来就重建连接(休眠唤醒后的死连接靠它发现)。 */
|
|
100
|
+
idleTimeoutMs?: number;
|
|
97
101
|
log?: Logger;
|
|
98
102
|
}
|
|
99
103
|
|
|
@@ -105,6 +109,12 @@ interface PendingSend {
|
|
|
105
109
|
/** 发送确认的默认超时。 */
|
|
106
110
|
export const DEFAULT_SEND_TIMEOUT_MS = 15_000;
|
|
107
111
|
|
|
112
|
+
/** 心跳间隔:主动 ping 一次,顺便刷新活跃时间。 */
|
|
113
|
+
export const DEFAULT_HEARTBEAT_MS = 20_000;
|
|
114
|
+
|
|
115
|
+
/** 多久没有任何往来就认定连接已死。桥每 20 秒 ping 一次,留三倍余量。 */
|
|
116
|
+
export const DEFAULT_IDLE_TIMEOUT_MS = 60_000;
|
|
117
|
+
|
|
108
118
|
export class AstrbotBridgeDriver implements BridgeDriver {
|
|
109
119
|
private readonly opts: BridgeDriverOptions;
|
|
110
120
|
private readonly log: Logger;
|
|
@@ -112,6 +122,8 @@ export class AstrbotBridgeDriver implements BridgeDriver {
|
|
|
112
122
|
private stopped = true;
|
|
113
123
|
private backoffMs: number;
|
|
114
124
|
private reconnectTimer?: ReturnType<typeof setTimeout>;
|
|
125
|
+
private heartbeat?: ReturnType<typeof setInterval>;
|
|
126
|
+
private lastActivity = 0;
|
|
115
127
|
private ridSeq = 0;
|
|
116
128
|
private pending = new Map<string, PendingSend>();
|
|
117
129
|
private messageCbs: Array<(m: BridgeMessage) => void> = [];
|
|
@@ -167,6 +179,7 @@ export class AstrbotBridgeDriver implements BridgeDriver {
|
|
|
167
179
|
|
|
168
180
|
async stop(): Promise<void> {
|
|
169
181
|
this.stopped = true;
|
|
182
|
+
this.stopHeartbeat();
|
|
170
183
|
if (this.reconnectTimer) {
|
|
171
184
|
clearTimeout(this.reconnectTimer);
|
|
172
185
|
this.reconnectTimer = undefined;
|
|
@@ -273,11 +286,24 @@ export class AstrbotBridgeDriver implements BridgeDriver {
|
|
|
273
286
|
});
|
|
274
287
|
}
|
|
275
288
|
this.emitState(true);
|
|
289
|
+
this.startHeartbeat(ws);
|
|
276
290
|
settle();
|
|
277
291
|
});
|
|
278
292
|
|
|
279
293
|
ws.on('message', (data) => {
|
|
280
|
-
if (!this.stopped && this.ws === ws)
|
|
294
|
+
if (!this.stopped && this.ws === ws) {
|
|
295
|
+
this.lastActivity = Date.now();
|
|
296
|
+
this.handleFrame(String(data));
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// 桥每 20 秒 ping 一次;收到任何一帧都算「还活着」。
|
|
301
|
+
ws.on('ping', () => {
|
|
302
|
+
if (this.ws === ws) this.lastActivity = Date.now();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
ws.on('pong', () => {
|
|
306
|
+
if (this.ws === ws) this.lastActivity = Date.now();
|
|
281
307
|
});
|
|
282
308
|
|
|
283
309
|
ws.on('error', (err) => {
|
|
@@ -290,6 +316,7 @@ export class AstrbotBridgeDriver implements BridgeDriver {
|
|
|
290
316
|
const isCurrent = this.ws === ws;
|
|
291
317
|
const wasConnected = this.connected;
|
|
292
318
|
if (isCurrent) {
|
|
319
|
+
this.stopHeartbeat();
|
|
293
320
|
this.ws = undefined;
|
|
294
321
|
this.connected = false;
|
|
295
322
|
this.rejectAllPending('与桥的连接已断开');
|
|
@@ -303,6 +330,43 @@ export class AstrbotBridgeDriver implements BridgeDriver {
|
|
|
303
330
|
});
|
|
304
331
|
}
|
|
305
332
|
|
|
333
|
+
/**
|
|
334
|
+
* 连接死掉时 socket 不一定给我们 close:机器休眠、网络切换、对端进程消失都可能留下
|
|
335
|
+
* 一条「开着但永远没有下文」的连接。定期 ping,超过 idleTimeoutMs 没有任何往来就主动
|
|
336
|
+
* terminate,让 close 走正常重连。
|
|
337
|
+
*/
|
|
338
|
+
private startHeartbeat(ws: WebSocket): void {
|
|
339
|
+
this.stopHeartbeat();
|
|
340
|
+
const every = this.opts.heartbeatMs ?? DEFAULT_HEARTBEAT_MS;
|
|
341
|
+
if (every <= 0) return;
|
|
342
|
+
const idleLimit = this.opts.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
343
|
+
this.lastActivity = Date.now();
|
|
344
|
+
this.heartbeat = setInterval(() => {
|
|
345
|
+
if (this.ws !== ws) return;
|
|
346
|
+
const idle = Date.now() - this.lastActivity;
|
|
347
|
+
if (idle > idleLimit) {
|
|
348
|
+
this.log.warn('桥连接长时间没有任何动静,重建连接', {
|
|
349
|
+
idleMs: idle,
|
|
350
|
+
idleLimitMs: idleLimit,
|
|
351
|
+
});
|
|
352
|
+
ws.terminate();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
ws.ping();
|
|
357
|
+
} catch {
|
|
358
|
+
// 发不出去就等 close 接手,这里不再多记一条。
|
|
359
|
+
}
|
|
360
|
+
}, every);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private stopHeartbeat(): void {
|
|
364
|
+
if (this.heartbeat) {
|
|
365
|
+
clearInterval(this.heartbeat);
|
|
366
|
+
this.heartbeat = undefined;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
306
370
|
private scheduleReconnect(): void {
|
|
307
371
|
if (this.reconnectTimer || this.stopped) return;
|
|
308
372
|
const delay = this.backoffMs;
|