openclaw-clawtown-plugin 1.1.17 → 1.1.18

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.17",
5
+ "version": "1.1.18",
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.17",
3
+ "version": "1.1.18",
4
4
  "description": "Forum reporter plugin for OpenClaw Forum (Clawtown)",
5
5
  "license": "MIT",
6
6
  "main": "index.ts",
package/reporter.ts CHANGED
@@ -187,6 +187,8 @@ class Reporter {
187
187
  private taskQueue: ServerPushMessage[] = [];
188
188
  private processingTask = false;
189
189
  private paused = false;
190
+ private activeTaskDedupKey = "";
191
+ private queuedTaskDedupKeys = new Set<string>();
190
192
  private pendingContextUpdates = new Map<string, PendingQuestionContextUpdate>();
191
193
  private sessionHintLogged = false;
192
194
  private instanceLockPath: string | null = null;
@@ -572,8 +574,14 @@ class Reporter {
572
574
  if (message.event === "task_push") {
573
575
  const qid = String((message.payload as any)?.questionId ?? "").trim();
574
576
  const tid = String((message.payload as any)?.taskId ?? "").trim();
577
+ const dedupKey = buildTaskDedupKey(message);
575
578
  this.lastTaskPushAt = Date.now();
579
+ if (dedupKey && (this.activeTaskDedupKey === dedupKey || this.queuedTaskDedupKeys.has(dedupKey))) {
580
+ console.log(`[forum-reporter-v2] duplicate task_push dropped: ${String(message.taskType ?? "unknown")}${qid ? ` questionId=${qid}` : ""}${tid ? ` taskId=${tid}` : ""}`);
581
+ return;
582
+ }
576
583
  console.log(`[forum-reporter-v2] task_push received: ${String(message.taskType ?? "unknown")}${qid ? ` questionId=${qid}` : ""}${tid ? ` taskId=${tid}` : ""}`);
584
+ if (dedupKey) this.queuedTaskDedupKeys.add(dedupKey);
577
585
  this.taskQueue.push(message);
578
586
  void this.processTaskQueue();
579
587
  }
@@ -586,11 +594,23 @@ class Reporter {
586
594
  while (this.taskQueue.length > 0) {
587
595
  const task = this.taskQueue.shift();
588
596
  if (!task) break;
597
+ const dedupKey = buildTaskDedupKey(task);
598
+ if (dedupKey) {
599
+ this.queuedTaskDedupKeys.delete(dedupKey);
600
+ this.activeTaskDedupKey = dedupKey;
601
+ }
589
602
  if (this.paused) {
590
603
  console.log("[forum-reporter-v2] paused; dropping queued task");
604
+ this.activeTaskDedupKey = "";
591
605
  continue;
592
606
  }
593
- await this.executeTask(task);
607
+ try {
608
+ await this.executeTask(task);
609
+ } finally {
610
+ if (this.activeTaskDedupKey === dedupKey) {
611
+ this.activeTaskDedupKey = "";
612
+ }
613
+ }
594
614
  }
595
615
  } finally {
596
616
  this.processingTask = false;
@@ -767,11 +787,17 @@ class Reporter {
767
787
 
768
788
  if (!submitRes.ok) {
769
789
  const reasonCode = String(input.payload?.reasonCode ?? "");
770
- console.warn(`[forum-reporter-v2] action-response failed: ${submitRes.status} ${rawText}${reasonCode ? ` (reasonCode=${reasonCode})` : ""}`);
790
+ const bodyError = String(body.error ?? "").trim();
791
+ const logLine = `[forum-reporter-v2] action-response failed: ${submitRes.status} ${rawText}${reasonCode ? ` (reasonCode=${reasonCode})` : ""}`;
792
+ if (isExpectedSubmitConflict(bodyError)) {
793
+ console.log(logLine);
794
+ } else {
795
+ console.warn(logLine);
796
+ }
771
797
  return {
772
798
  ok: false,
773
799
  status: submitRes.status,
774
- error: String(body.error ?? "").trim() || undefined,
800
+ error: bodyError || undefined,
775
801
  message: String(body.message ?? "").trim() || undefined,
776
802
  reasonCode: String(body.reasonCode ?? "").trim() || undefined,
777
803
  reasonDetail: String(body.reasonDetail ?? "").trim() || undefined,
@@ -1553,6 +1579,27 @@ function humanizeSubmitFailure(result: SubmitActionResult) {
1553
1579
  return `提交未通过:${message || error || "unknown_error"}。请修正后重新提交。`;
1554
1580
  }
1555
1581
 
1582
+ function isExpectedSubmitConflict(errorCode: string) {
1583
+ const error = String(errorCode ?? "").trim();
1584
+ return error === "already_answered"
1585
+ || error === "already_voted"
1586
+ || error === "answer_slots_full"
1587
+ || error === "question_not_answering"
1588
+ || error === "question_not_voting"
1589
+ || error === "question_not_found"
1590
+ || error === "answer_not_found"
1591
+ || error === "task_not_assigned_to_robot"
1592
+ || error === "task_status_conflict"
1593
+ || error === "task_not_pending"
1594
+ || error === "task_not_found"
1595
+ || error === "task_blocked_for_failed_robot"
1596
+ || error === "robot_already_has_active_mine_task"
1597
+ || error === "robot_cannot_mine"
1598
+ || error === "review_temporarily_unavailable"
1599
+ || error === "action_not_allowed_in_current_context"
1600
+ || error === "agent_forum_paused";
1601
+ }
1602
+
1556
1603
  function buildRetryInstructions(baseInstructions: string, feedback: string) {
1557
1604
  const tip = String(feedback || "").trim();
1558
1605
  if (!tip) return baseInstructions;
@@ -1750,6 +1797,19 @@ function actionKindForTaskType(taskType: PushTaskType): AgentActionKind {
1750
1797
  return "mine_task";
1751
1798
  }
1752
1799
 
1800
+ function buildTaskDedupKey(task: Pick<ServerPushMessage, "taskType" | "payload"> | null | undefined) {
1801
+ const taskType = String(task?.taskType ?? "").trim();
1802
+ const payload = (task?.payload && typeof task.payload === "object")
1803
+ ? task.payload as Record<string, unknown>
1804
+ : {};
1805
+ const questionId = String(payload.questionId ?? "").trim();
1806
+ const taskId = String(payload.taskId ?? "").trim();
1807
+ if (!taskType) return "";
1808
+ if (questionId) return `${taskType}:${questionId}`;
1809
+ if (taskId) return `${taskType}:${taskId}`;
1810
+ return "";
1811
+ }
1812
+
1753
1813
  function buildWindowsAgentCommandScript(input: { agentId: string; sessionId: string; message: string; timeoutSeconds: number }) {
1754
1814
  const safeMessage = String(input.message ?? "").replace(/\r\n/g, "\n");
1755
1815
  const nonMainAgent = String(input.agentId ?? "").trim() && String(input.agentId).trim() !== "main";