openclaw-clawtown-plugin 1.1.35 → 1.1.37

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.
@@ -2,7 +2,7 @@
2
2
  "id": "openclaw-clawtown-plugin",
3
3
  "name": "OpenClaw Clawtown Plugin",
4
4
  "description": "Connects an OpenClaw agent to OpenClaw Forum and reports forum actions",
5
- "version": "1.1.35",
5
+ "version": "1.1.37",
6
6
  "main": "./index.ts",
7
7
  "configSchema": {
8
8
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-clawtown-plugin",
3
- "version": "1.1.35",
3
+ "version": "1.1.37",
4
4
  "description": "Forum reporter plugin for OpenClaw Forum (Clawtown)",
5
5
  "license": "MIT",
6
6
  "main": "index.ts",
package/reporter.ts CHANGED
@@ -32,9 +32,10 @@ const HEARTBEAT_INTERVAL_MS = 60_000;
32
32
  const POLL_FALLBACK_MIN_INTERVAL_MS = 60_000;
33
33
  const POLL_FORCE_AFTER_NO_PUSH_MS = 90_000;
34
34
  const RECONNECT_BASE_MS = 3_000;
35
- const RECONNECT_MAX_MS = 5 * 60_000;
35
+ const RECONNECT_MAX_MS = 60_000;
36
36
  const CONNECTION_SELF_HEAL_INTERVAL_MS = 30_000;
37
37
  const CONNECTING_STALE_MS = 20_000;
38
+ const SERVER_SILENT_FORCE_RECONNECT_MS = 180_000;
38
39
  const FAILED_MINE_TASK_LOCAL_SUPPRESS_MS = 24 * 60 * 60_000;
39
40
  const FORUM_ISOLATED_HOME_DIRNAME = ".openclaw-forum";
40
41
  const DEFAULT_OPENCLAW_HOME_DIRNAME = ".openclaw";
@@ -104,7 +105,7 @@ function normalizeServerUrl(raw: string) {
104
105
 
105
106
  type AgentActionKind = "answer_question" | "vote_question" | "mine_task";
106
107
  type PushTaskType = "answer_question" | "vote_question" | "mine_draft" | "mine_followup";
107
- type PushEvent = "init" | "task_push" | "context_update" | "task_cancel" | "pause" | "resume" | "idle";
108
+ type PushEvent = "init" | "task_push" | "context_update" | "task_cancel" | "pause" | "resume" | "idle" | "heartbeat_ack";
108
109
  const MIN_ANSWER_LENGTH = 800;
109
110
  const MAX_ANSWER_SUMMARY_LENGTH = 200;
110
111
 
@@ -255,6 +256,7 @@ class Reporter {
255
256
  private shuttingDown = false;
256
257
  private lastTaskPushAt = 0;
257
258
  private lastPollAt = 0;
259
+ private lastServerDataAt = 0;
258
260
 
259
261
  private taskQueue: ServerPushMessage[] = [];
260
262
  private processingTask = false;
@@ -536,12 +538,14 @@ class Reporter {
536
538
  this.wsFailureCount = 0;
537
539
  this.reconnectDelayMs = RECONNECT_BASE_MS;
538
540
  this.wsConnectStartedAt = 0;
541
+ this.lastServerDataAt = Date.now();
539
542
  this.startHeartbeat();
540
543
  console.log("[forum-reporter-v2] WebSocket connected");
541
544
  void this.syncProfile(true);
542
545
  };
543
546
 
544
547
  ws.onmessage = (event) => {
548
+ this.lastServerDataAt = Date.now();
545
549
  void this.handleServerMessage(String(event.data ?? ""));
546
550
  };
547
551
 
@@ -575,7 +579,7 @@ class Reporter {
575
579
  this.reconnectTimer = null;
576
580
  this.connectWebSocket();
577
581
  }, this.reconnectDelayMs);
578
- this.reconnectDelayMs = Math.min(RECONNECT_MAX_MS, Math.floor(this.reconnectDelayMs * 1.8));
582
+ this.reconnectDelayMs = Math.min(RECONNECT_MAX_MS, Math.floor(this.reconnectDelayMs * 1.5));
579
583
  }
580
584
 
581
585
  private ensureConnectionSelfHeal() {
@@ -608,6 +612,17 @@ class Reporter {
608
612
  if (this.heartbeatTimer) return;
609
613
  this.heartbeatTimer = setInterval(async () => {
610
614
  const now = Date.now();
615
+ const ws = this.ws;
616
+ const serverSilentMs = this.lastServerDataAt > 0 ? now - this.lastServerDataAt : 0;
617
+ if (serverSilentMs > SERVER_SILENT_FORCE_RECONNECT_MS && ws && ws.readyState === WebSocket.OPEN) {
618
+ console.warn(`[forum-reporter-v2] server silent for ${Math.round(serverSilentMs / 1000)}s, forcing reconnect`);
619
+ try { ws.close(); } catch {}
620
+ if (this.ws === ws) this.ws = null;
621
+ this.stopHeartbeat();
622
+ this.reconnectDelayMs = RECONNECT_BASE_MS;
623
+ if (!this.shuttingDown) this.scheduleReconnect();
624
+ return;
625
+ }
611
626
  this.sendWs({
612
627
  type: "heartbeat",
613
628
  });
@@ -638,7 +653,14 @@ class Reporter {
638
653
  if (!ws || ws.readyState !== WebSocket.OPEN) return;
639
654
  try {
640
655
  ws.send(JSON.stringify(payload));
641
- } catch {}
656
+ } catch {
657
+ console.warn("[forum-reporter-v2] ws send failed, connection dead, triggering reconnect");
658
+ try { ws.close(); } catch {}
659
+ if (this.ws === ws) this.ws = null;
660
+ this.stopHeartbeat();
661
+ this.reconnectDelayMs = RECONNECT_BASE_MS;
662
+ if (!this.shuttingDown) this.scheduleReconnect();
663
+ }
642
664
  }
643
665
 
644
666
  private async handleServerMessage(raw: string) {
@@ -683,6 +705,10 @@ class Reporter {
683
705
  return;
684
706
  }
685
707
 
708
+ if (message.event === "heartbeat_ack") {
709
+ return;
710
+ }
711
+
686
712
  if (message.event === "task_cancel") {
687
713
  console.log("[forum-reporter-v2] task cancelled by server");
688
714
  return;
@@ -1018,14 +1044,14 @@ class Reporter {
1018
1044
  };
1019
1045
  }
1020
1046
 
1021
- private async syncProfile(force = false) {
1022
- if (!force && Date.now() - this.lastProfileSyncAt < PROFILE_SYNC_MIN_INTERVAL_MS) return;
1047
+ private async syncProfile(force = false): Promise<SyncProfileResult> {
1048
+ if (!force && Date.now() - this.lastProfileSyncAt < PROFILE_SYNC_MIN_INTERVAL_MS) return { ok: true };
1023
1049
  const shouldRetry = force && !this.lastPairCodeShown;
1024
1050
  const maxAttempts = shouldRetry ? (1 + PROFILE_SYNC_RETRY_COUNT) : 1;
1025
1051
  let lastFailure: SyncProfileResult | null = null;
1026
1052
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1027
1053
  const result = await this.syncProfileOnce(force || attempt > 1);
1028
- if (result.ok) return;
1054
+ if (result.ok) return result;
1029
1055
  lastFailure = result;
1030
1056
  if (!shouldRetry || attempt >= maxAttempts || !shouldRetryAutoProvisionError(result.error, result.status)) {
1031
1057
  break;
@@ -1038,11 +1064,13 @@ class Reporter {
1038
1064
  }
1039
1065
  if (lastFailure?.status) {
1040
1066
  console.warn(`[forum-reporter-v2] profile sync failed: ${lastFailure.status}`);
1041
- return;
1067
+ return lastFailure;
1042
1068
  }
1043
1069
  if (lastFailure?.error) {
1044
1070
  console.warn(`[forum-reporter-v2] profile sync failed: ${describeAutoProvisionError(lastFailure.error)}`);
1071
+ return lastFailure;
1045
1072
  }
1073
+ return { ok: true };
1046
1074
  }
1047
1075
 
1048
1076
  private async syncProfileOnce(force = false): Promise<SyncProfileResult> {