@botcord/botcord 0.3.2-beta.20260407032921 → 0.3.2
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/index.ts +24 -25
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/botcord/SKILL.md +41 -0
- package/src/client.ts +10 -1
- package/src/commands/healthcheck.ts +28 -1
- package/src/constants.ts +1 -1
- package/src/credentials.ts +38 -0
- package/src/dynamic-context.ts +77 -0
- package/src/inbound.ts +11 -0
- package/src/memory-protocol.ts +48 -28
- package/src/memory.ts +61 -4
- package/src/onboarding-hook.ts +68 -0
- package/src/room-context.ts +42 -27
- package/src/tools/notify.ts +48 -31
- package/src/tools/tool-result.ts +119 -0
- package/src/tools/with-client.ts +93 -0
- package/src/tools/working-memory.ts +112 -23
- package/src/ws-client.ts +27 -3
package/src/ws-client.ts
CHANGED
|
@@ -28,17 +28,30 @@ interface WsClientOptions {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export type WsConnectionStatus = "disconnected" | "connecting" | "authenticated" | "reconnecting";
|
|
32
|
+
|
|
33
|
+
export interface WsClientEntry {
|
|
34
|
+
stop: () => void;
|
|
35
|
+
getStatus: () => WsConnectionStatus;
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
// Use lazy initialization to avoid TDZ errors when jiti resolves
|
|
32
39
|
// the dynamic import("./ws-client.js") before the module body completes.
|
|
33
|
-
let _activeWsClients: Map<string,
|
|
40
|
+
let _activeWsClients: Map<string, WsClientEntry> | undefined;
|
|
34
41
|
function getActiveWsClients() {
|
|
35
42
|
return (_activeWsClients ??= new Map());
|
|
36
43
|
}
|
|
37
44
|
|
|
45
|
+
/** Get the current WS connection status for an account. */
|
|
46
|
+
export function getWsStatus(accountId: string): WsConnectionStatus {
|
|
47
|
+
const entry = getActiveWsClients().get(accountId);
|
|
48
|
+
return entry ? entry.getStatus() : "disconnected";
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
// Reconnect backoff: 1s, 2s, 4s, 8s, 16s, 30s max
|
|
39
52
|
const RECONNECT_BACKOFF = [1000, 2000, 4000, 8000, 16000, 30000];
|
|
40
53
|
|
|
41
|
-
export function startWsClient(opts: WsClientOptions):
|
|
54
|
+
export function startWsClient(opts: WsClientOptions): WsClientEntry {
|
|
42
55
|
// Stop any existing client for this account before creating a new one
|
|
43
56
|
const existing = getActiveWsClients().get(opts.accountId);
|
|
44
57
|
if (existing) existing.stop();
|
|
@@ -55,6 +68,7 @@ export function startWsClient(opts: WsClientOptions): { stop: () => void } {
|
|
|
55
68
|
let pendingUpdate = false;
|
|
56
69
|
let keepaliveTimer: ReturnType<typeof setInterval> | null = null;
|
|
57
70
|
const KEEPALIVE_INTERVAL = 20_000; // 20s — well under Caddy/proxy 30s timeout
|
|
71
|
+
let status: WsConnectionStatus = "connecting";
|
|
58
72
|
|
|
59
73
|
async function fetchAndDispatch() {
|
|
60
74
|
if (processing) {
|
|
@@ -101,6 +115,7 @@ export function startWsClient(opts: WsClientOptions): { stop: () => void } {
|
|
|
101
115
|
const hubUrl = client.getHubUrl();
|
|
102
116
|
const wsUrl = buildHubWebSocketUrl(hubUrl);
|
|
103
117
|
|
|
118
|
+
status = "connecting";
|
|
104
119
|
log?.info(`[${dp}] WebSocket connecting to ${wsUrl}`);
|
|
105
120
|
ws = new WebSocket(wsUrl);
|
|
106
121
|
|
|
@@ -114,6 +129,7 @@ export function startWsClient(opts: WsClientOptions): { stop: () => void } {
|
|
|
114
129
|
const msg = JSON.parse(data.toString());
|
|
115
130
|
switch (msg.type) {
|
|
116
131
|
case "auth_ok":
|
|
132
|
+
status = "authenticated";
|
|
117
133
|
log?.info(`[${dp}] WebSocket authenticated as ${msg.agent_id}`);
|
|
118
134
|
reconnectAttempt = 0; // Reset backoff on successful auth
|
|
119
135
|
consecutiveAuthFailures = 0; // Reset auth failure counter
|
|
@@ -175,9 +191,11 @@ export function startWsClient(opts: WsClientOptions): { stop: () => void } {
|
|
|
175
191
|
if (code === 4001) {
|
|
176
192
|
consecutiveAuthFailures++;
|
|
177
193
|
if (consecutiveAuthFailures >= MAX_AUTH_FAILURES) {
|
|
194
|
+
status = "disconnected";
|
|
178
195
|
log?.error(`[${dp}] WebSocket auth failed ${consecutiveAuthFailures} times consecutively, stopping reconnect`);
|
|
179
196
|
return;
|
|
180
197
|
}
|
|
198
|
+
status = "reconnecting";
|
|
181
199
|
log?.warn(`[${dp}] WebSocket auth failed (${consecutiveAuthFailures}/${MAX_AUTH_FAILURES}), force-refreshing token before reconnect`);
|
|
182
200
|
// Await token refresh so the next connect() picks up the new token
|
|
183
201
|
try {
|
|
@@ -210,12 +228,14 @@ export function startWsClient(opts: WsClientOptions): { stop: () => void } {
|
|
|
210
228
|
const delay =
|
|
211
229
|
RECONNECT_BACKOFF[Math.min(reconnectAttempt, RECONNECT_BACKOFF.length - 1)];
|
|
212
230
|
reconnectAttempt++;
|
|
231
|
+
status = "reconnecting";
|
|
213
232
|
log?.info(`[${dp}] WebSocket reconnecting in ${delay}ms (attempt ${reconnectAttempt})`);
|
|
214
233
|
reconnectTimer = setTimeout(connect, delay);
|
|
215
234
|
}
|
|
216
235
|
|
|
217
236
|
function stop() {
|
|
218
237
|
running = false;
|
|
238
|
+
status = "disconnected";
|
|
219
239
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
220
240
|
if (keepaliveTimer) { clearInterval(keepaliveTimer); keepaliveTimer = null; }
|
|
221
241
|
if (ws) {
|
|
@@ -229,10 +249,14 @@ export function startWsClient(opts: WsClientOptions): { stop: () => void } {
|
|
|
229
249
|
getActiveWsClients().delete(accountId);
|
|
230
250
|
}
|
|
231
251
|
|
|
252
|
+
function getStatus(): WsConnectionStatus {
|
|
253
|
+
return status;
|
|
254
|
+
}
|
|
255
|
+
|
|
232
256
|
// Start connection
|
|
233
257
|
connect();
|
|
234
258
|
|
|
235
|
-
const entry = { stop };
|
|
259
|
+
const entry: WsClientEntry = { stop, getStatus };
|
|
236
260
|
getActiveWsClients().set(accountId, entry);
|
|
237
261
|
|
|
238
262
|
abortSignal?.addEventListener("abort", stop, { once: true });
|