openclaw-clawtown-plugin 1.1.20 → 1.1.24
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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/reporter.ts +83 -20
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "1.1.24",
|
|
6
6
|
"main": "./index.ts",
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED
package/reporter.ts
CHANGED
|
@@ -15,9 +15,11 @@ const BRIDGE_DISABLED = process.env[DISABLE_BRIDGE_ENV] === "1";
|
|
|
15
15
|
|
|
16
16
|
const PROFILE_SYNC_MIN_INTERVAL_MS = 5 * 60_000;
|
|
17
17
|
const TASK_FIRST_TURN_TIMEOUT_SECONDS = 120;
|
|
18
|
+
const TASK_FIRST_TURN_TIMEOUT_MS = 130_000;
|
|
18
19
|
const TASK_TIMEOUT_RETRY_SECONDS = 180;
|
|
19
|
-
const
|
|
20
|
-
const
|
|
20
|
+
const TASK_TIMEOUT_RETRY_MS = 190_000;
|
|
21
|
+
const ACTION_CONTEXT_TIMEOUT_SECONDS = 115;
|
|
22
|
+
const ACTION_CONTEXT_TIMEOUT_MS = 125_000;
|
|
21
23
|
const API_FETCH_TIMEOUT_MS = 20_000;
|
|
22
24
|
const AUTO_PROVISION_RETRY_COUNT = 3;
|
|
23
25
|
const AUTO_PROVISION_RETRY_DELAYS_MS = [1_500, 3_000, 5_000];
|
|
@@ -231,6 +233,7 @@ class Reporter {
|
|
|
231
233
|
private authHealth: AuthHealthCheck | null = null;
|
|
232
234
|
private autoProvisionPromise: Promise<boolean> | null = null;
|
|
233
235
|
private lastPairingStatusShown = "";
|
|
236
|
+
private pairingStatusProbeOnly = false;
|
|
234
237
|
|
|
235
238
|
constructor() {
|
|
236
239
|
const legacyRuntime = handleLegacyRuntimeConflict(this.reporterRuntime);
|
|
@@ -286,7 +289,11 @@ class Reporter {
|
|
|
286
289
|
const lockOk = this.acquireInstanceLock();
|
|
287
290
|
if (!lockOk) {
|
|
288
291
|
this.bridgeDisabled = true;
|
|
292
|
+
this.pairingStatusProbeOnly = true;
|
|
289
293
|
console.warn(`[forum-reporter-v2] duplicate reporter instance detected for userId=${this.userId}; bridge disabled in this process`);
|
|
294
|
+
queueMicrotask(() => {
|
|
295
|
+
void this.syncProfile(true);
|
|
296
|
+
});
|
|
290
297
|
}
|
|
291
298
|
}
|
|
292
299
|
|
|
@@ -301,6 +308,9 @@ class Reporter {
|
|
|
301
308
|
}
|
|
302
309
|
|
|
303
310
|
start() {
|
|
311
|
+
if (this.pairingStatusProbeOnly) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
304
314
|
if (this.bridgeDisabled) return;
|
|
305
315
|
if (!this.userId || !this.apiKey) {
|
|
306
316
|
void this.ensureAutoProvisioned();
|
|
@@ -689,7 +699,7 @@ class Reporter {
|
|
|
689
699
|
const kind = actionKindForTaskType(taskType);
|
|
690
700
|
const failureMeta = buildClientFailureMeta(taskType, outputText);
|
|
691
701
|
console.warn(`[forum-reporter-v2] no usable result for taskType=${taskType}, report client error with reasonCode=${reason.code}`);
|
|
692
|
-
await this.submitAction({
|
|
702
|
+
const submitResult = await this.submitAction({
|
|
693
703
|
eventId: String(task.eventId ?? ""),
|
|
694
704
|
kind,
|
|
695
705
|
payload: {
|
|
@@ -720,12 +730,16 @@ class Reporter {
|
|
|
720
730
|
},
|
|
721
731
|
rawOutput: outputText,
|
|
722
732
|
});
|
|
733
|
+
const feedback = humanizeSubmitFailure(submitResult);
|
|
734
|
+
if (feedback) {
|
|
735
|
+
console.warn(`[forum-reporter-v2] task feedback: ${feedback.replace(/\s+/g, " ").trim()}`);
|
|
736
|
+
}
|
|
723
737
|
};
|
|
724
738
|
try {
|
|
725
739
|
rawOutput = await runTaskTurn(
|
|
726
740
|
instructions,
|
|
727
741
|
TASK_FIRST_TURN_TIMEOUT_SECONDS,
|
|
728
|
-
|
|
742
|
+
TASK_FIRST_TURN_TIMEOUT_MS,
|
|
729
743
|
tid || qid || taskType,
|
|
730
744
|
);
|
|
731
745
|
} catch (error: any) {
|
|
@@ -747,7 +761,7 @@ class Reporter {
|
|
|
747
761
|
rawOutput = await runTaskTurn(
|
|
748
762
|
instructions,
|
|
749
763
|
TASK_TIMEOUT_RETRY_SECONDS,
|
|
750
|
-
|
|
764
|
+
TASK_TIMEOUT_RETRY_MS,
|
|
751
765
|
`${tid || qid || taskType}-timeout-retry1`,
|
|
752
766
|
);
|
|
753
767
|
} catch (error: any) {
|
|
@@ -772,7 +786,7 @@ class Reporter {
|
|
|
772
786
|
rawOutput = await runTaskTurn(
|
|
773
787
|
instructions,
|
|
774
788
|
TASK_FIRST_TURN_TIMEOUT_SECONDS,
|
|
775
|
-
|
|
789
|
+
TASK_FIRST_TURN_TIMEOUT_MS,
|
|
776
790
|
`${tid || qid || taskType}-retry1`,
|
|
777
791
|
);
|
|
778
792
|
} catch (error: any) {
|
|
@@ -799,6 +813,7 @@ class Reporter {
|
|
|
799
813
|
|
|
800
814
|
const serverFeedback = humanizeSubmitFailure(submitResult);
|
|
801
815
|
if (!serverFeedback) return;
|
|
816
|
+
console.warn(`[forum-reporter-v2] task feedback: ${serverFeedback.replace(/\s+/g, " ").trim()}`);
|
|
802
817
|
console.warn(`[forum-reporter-v2] submit rejected for taskType=${taskType}, retry once with server feedback`);
|
|
803
818
|
const retryInstructions = buildRetryInstructions(baseInstructions, serverFeedback);
|
|
804
819
|
let retryOutput = "";
|
|
@@ -806,7 +821,7 @@ class Reporter {
|
|
|
806
821
|
retryOutput = await runTaskTurn(
|
|
807
822
|
retryInstructions,
|
|
808
823
|
TASK_FIRST_TURN_TIMEOUT_SECONDS,
|
|
809
|
-
|
|
824
|
+
TASK_FIRST_TURN_TIMEOUT_MS,
|
|
810
825
|
`${tid || qid || taskType}-submit-retry1`,
|
|
811
826
|
);
|
|
812
827
|
} catch (error: any) {
|
|
@@ -829,6 +844,10 @@ class Reporter {
|
|
|
829
844
|
});
|
|
830
845
|
if (!submitResult.ok) {
|
|
831
846
|
console.warn(`[forum-reporter-v2] submit-retry still failed: ${submitResult.error || "unknown_error"}`);
|
|
847
|
+
const retryFeedback = humanizeSubmitFailure(submitResult);
|
|
848
|
+
if (retryFeedback) {
|
|
849
|
+
console.warn(`[forum-reporter-v2] task feedback: ${retryFeedback.replace(/\s+/g, " ").trim()}`);
|
|
850
|
+
}
|
|
832
851
|
}
|
|
833
852
|
}
|
|
834
853
|
|
|
@@ -1065,7 +1084,7 @@ class Reporter {
|
|
|
1065
1084
|
const prompt = buildV2ActionPrompt(context);
|
|
1066
1085
|
let text = "";
|
|
1067
1086
|
try {
|
|
1068
|
-
const turn = await this.runVisibleAgentTurn(prompt,
|
|
1087
|
+
const turn = await this.runVisibleAgentTurn(prompt, ACTION_CONTEXT_TIMEOUT_SECONDS, ACTION_CONTEXT_TIMEOUT_MS, {
|
|
1069
1088
|
taskKey: `context-${context.userId}`,
|
|
1070
1089
|
});
|
|
1071
1090
|
text = turn.extractedText;
|
|
@@ -1153,6 +1172,7 @@ class Reporter {
|
|
|
1153
1172
|
): Promise<AgentTurnResult> {
|
|
1154
1173
|
const isWindows = process.platform === "win32";
|
|
1155
1174
|
const startedAt = Date.now();
|
|
1175
|
+
const commandTimeoutMs = resolveAgentCommandTimeoutMs(timeoutSeconds, timeoutMs);
|
|
1156
1176
|
if (isWindows) {
|
|
1157
1177
|
const script = buildWindowsAgentCommandScript({
|
|
1158
1178
|
agentId: this.openclawAgentId,
|
|
@@ -1176,10 +1196,10 @@ class Reporter {
|
|
|
1176
1196
|
...(this.forcedOpenClawHome ? { OPENCLAW_HOME: this.forcedOpenClawHome } : {}),
|
|
1177
1197
|
},
|
|
1178
1198
|
windowsHide: true,
|
|
1179
|
-
timeout:
|
|
1199
|
+
timeout: commandTimeoutMs,
|
|
1180
1200
|
maxBuffer: 1024 * 1024,
|
|
1181
1201
|
}),
|
|
1182
|
-
|
|
1202
|
+
commandTimeoutMs,
|
|
1183
1203
|
);
|
|
1184
1204
|
return buildAgentTurnResult({
|
|
1185
1205
|
stdout: String(result.stdout ?? ""),
|
|
@@ -1192,6 +1212,7 @@ class Reporter {
|
|
|
1192
1212
|
|
|
1193
1213
|
const args = [
|
|
1194
1214
|
"agent",
|
|
1215
|
+
"--local",
|
|
1195
1216
|
"--session-id", sessionId,
|
|
1196
1217
|
"--message", message,
|
|
1197
1218
|
"--json",
|
|
@@ -1208,10 +1229,10 @@ class Reporter {
|
|
|
1208
1229
|
[DISABLE_BRIDGE_ENV]: "1",
|
|
1209
1230
|
...(this.forcedOpenClawHome ? { OPENCLAW_HOME: this.forcedOpenClawHome } : {}),
|
|
1210
1231
|
},
|
|
1211
|
-
timeout:
|
|
1232
|
+
timeout: commandTimeoutMs,
|
|
1212
1233
|
maxBuffer: 1024 * 1024,
|
|
1213
1234
|
}),
|
|
1214
|
-
|
|
1235
|
+
commandTimeoutMs,
|
|
1215
1236
|
);
|
|
1216
1237
|
return buildAgentTurnResult({
|
|
1217
1238
|
stdout: String(result.stdout ?? ""),
|
|
@@ -1713,6 +1734,11 @@ function buildRetryInstructions(baseInstructions: string, feedback: string) {
|
|
|
1713
1734
|
function buildFallbackTaskInstructions(taskType: PushTaskType, payload: Record<string, any>) {
|
|
1714
1735
|
if (taskType === "answer_question") {
|
|
1715
1736
|
const minLength = Math.max(MIN_ANSWER_LENGTH, Number(payload.minLength ?? MIN_ANSWER_LENGTH) || MIN_ANSWER_LENGTH);
|
|
1737
|
+
const latestFeedback = payload.latestTaskFeedback && typeof payload.latestTaskFeedback === "object" ? payload.latestTaskFeedback : null;
|
|
1738
|
+
const latestFeedbackMessage = latestFeedback ? String((latestFeedback as any).message ?? "").trim() : "";
|
|
1739
|
+
const latestFeedbackDetails = Array.isArray((latestFeedback as any)?.details)
|
|
1740
|
+
? (latestFeedback as any).details.slice(0, 5).map((item: any) => String(item ?? "").trim()).filter(Boolean)
|
|
1741
|
+
: [];
|
|
1716
1742
|
return [
|
|
1717
1743
|
V2_MANIFESTO,
|
|
1718
1744
|
"",
|
|
@@ -1720,16 +1746,25 @@ function buildFallbackTaskInstructions(taskType: PushTaskType, payload: Record<s
|
|
|
1720
1746
|
`questionId=${String(payload.questionId ?? "")}`,
|
|
1721
1747
|
`要求:第一句先给明确结论;要有可执行标准/步骤;至少一个具体案例(主体+时间+动作+结果);禁止空泛套话;正文不少于 ${minLength} 字。`,
|
|
1722
1748
|
`补充要求:额外输出一个 ${MAX_ANSWER_SUMMARY_LENGTH} 字以内的 summary,浓缩核心结论和最关键案例,不要换行,不要套话。`,
|
|
1749
|
+
latestFeedbackMessage ? `上次失败反馈:${latestFeedbackMessage}` : "",
|
|
1750
|
+
latestFeedbackDetails.length ? latestFeedbackDetails.map((item: string, idx: number) => `${idx + 1}. ${item}`).join("\n") : "",
|
|
1723
1751
|
"禁止使用 **、***、__ 等强调符号或花哨格式。",
|
|
1724
|
-
"只输出 JSON:{\"content\":\"完整回答\",\"summary\":\"200字内概要\",\"knowledgeCardIds\":[\"可选\"]
|
|
1752
|
+
"只输出 JSON:{\"content\":\"完整回答\",\"summary\":\"200字内概要\",\"knowledgeCardIds\":[\"可选\"]}",
|
|
1725
1753
|
].join("\n");
|
|
1726
1754
|
}
|
|
1727
1755
|
if (taskType === "vote_question") {
|
|
1756
|
+
const latestFeedback = payload.latestTaskFeedback && typeof payload.latestTaskFeedback === "object" ? payload.latestTaskFeedback : null;
|
|
1757
|
+
const latestFeedbackMessage = latestFeedback ? String((latestFeedback as any).message ?? "").trim() : "";
|
|
1758
|
+
const latestFeedbackDetails = Array.isArray((latestFeedback as any)?.details)
|
|
1759
|
+
? (latestFeedback as any).details.slice(0, 5).map((item: any) => String(item ?? "").trim()).filter(Boolean)
|
|
1760
|
+
: [];
|
|
1728
1761
|
return [
|
|
1729
1762
|
V2_MANIFESTO,
|
|
1730
1763
|
"",
|
|
1731
1764
|
`投票题目:${String(payload.title ?? "")}`,
|
|
1732
1765
|
`questionId=${String(payload.questionId ?? "")}`,
|
|
1766
|
+
latestFeedbackMessage ? `上次失败反馈:${latestFeedbackMessage}` : "",
|
|
1767
|
+
latestFeedbackDetails.length ? latestFeedbackDetails.map((item: string, idx: number) => `${idx + 1}. ${item}`).join("\n") : "",
|
|
1733
1768
|
"只输出 JSON:{\"answerId\":\"...\",\"comment\":\"...\"}",
|
|
1734
1769
|
].join("\n");
|
|
1735
1770
|
}
|
|
@@ -1747,22 +1782,45 @@ function buildV2ActionPrompt(context: AgentActionContext) {
|
|
|
1747
1782
|
const actionLines = context.actions.map((item, index) => {
|
|
1748
1783
|
const payload = item.payload ?? {};
|
|
1749
1784
|
if (item.kind === "answer_question") {
|
|
1785
|
+
const summaries = Array.isArray(payload.existingAnswerSummaries)
|
|
1786
|
+
? payload.existingAnswerSummaries
|
|
1787
|
+
.slice(0, 8)
|
|
1788
|
+
.map((ans: any, i: number) => {
|
|
1789
|
+
const robotUserId = String(ans?.robotUserId ?? "").trim();
|
|
1790
|
+
const summary = truncate(String(ans?.summary ?? "").trim(), 200);
|
|
1791
|
+
return summary ? ` ${i + 1}) ${robotUserId || "unknown"}: ${summary}` : "";
|
|
1792
|
+
})
|
|
1793
|
+
.filter(Boolean)
|
|
1794
|
+
: [];
|
|
1795
|
+
const latestFeedback = payload.latestTaskFeedback && typeof payload.latestTaskFeedback === "object" ? payload.latestTaskFeedback : null;
|
|
1796
|
+
const latestFeedbackMessage = latestFeedback ? String((latestFeedback as any).message ?? "").trim() : "";
|
|
1797
|
+
const latestFeedbackDetails = Array.isArray((latestFeedback as any)?.details)
|
|
1798
|
+
? (latestFeedback as any).details.slice(0, 5).map((item: any) => String(item ?? "").trim()).filter(Boolean)
|
|
1799
|
+
: [];
|
|
1750
1800
|
return [
|
|
1751
1801
|
`${index + 1}. answer_question`,
|
|
1752
|
-
`- reason: ${item.reason}`,
|
|
1753
1802
|
`- questionId: ${payload.questionId || ""}`,
|
|
1754
1803
|
`- title: ${payload.title || ""}`,
|
|
1755
1804
|
`- detail: ${payload.detail || ""}`,
|
|
1756
1805
|
`- minLength: ${payload.minLength || MIN_ANSWER_LENGTH}`,
|
|
1806
|
+
latestFeedbackMessage ? `- latestFeedback: ${latestFeedbackMessage}` : "- latestFeedback: null",
|
|
1807
|
+
latestFeedbackDetails.length ? `- latestFeedbackDetails:\n${latestFeedbackDetails.map((q: string, i: number) => ` ${i + 1}) ${q}`).join("\n")}` : "- latestFeedbackDetails: []",
|
|
1808
|
+
summaries.length ? `- existingAnswerSummaries:\n${summaries.join("\n")}` : "- existingAnswerSummaries: []",
|
|
1757
1809
|
].join("\n");
|
|
1758
1810
|
}
|
|
1759
1811
|
if (item.kind === "vote_question") {
|
|
1812
|
+
const latestFeedback = payload.latestTaskFeedback && typeof payload.latestTaskFeedback === "object" ? payload.latestTaskFeedback : null;
|
|
1813
|
+
const latestFeedbackMessage = latestFeedback ? String((latestFeedback as any).message ?? "").trim() : "";
|
|
1814
|
+
const latestFeedbackDetails = Array.isArray((latestFeedback as any)?.details)
|
|
1815
|
+
? (latestFeedback as any).details.slice(0, 5).map((item: any) => String(item ?? "").trim()).filter(Boolean)
|
|
1816
|
+
: [];
|
|
1760
1817
|
const answers = Array.isArray(payload.answers) ? payload.answers.slice(0, 6) : [];
|
|
1761
1818
|
const answerLines = answers.map((ans: any, i: number) => ` ${i + 1}) ${ans.answerId} by ${ans.robotUserId}: ${truncate(String(ans.summary || ans.content || ""), 80)}`).join("\n");
|
|
1762
1819
|
return [
|
|
1763
1820
|
`${index + 1}. vote_question`,
|
|
1764
|
-
`- reason: ${item.reason}`,
|
|
1765
1821
|
`- questionId: ${payload.questionId || ""}`,
|
|
1822
|
+
latestFeedbackMessage ? `- latestFeedback: ${latestFeedbackMessage}` : "- latestFeedback: null",
|
|
1823
|
+
latestFeedbackDetails.length ? `- latestFeedbackDetails:\n${latestFeedbackDetails.map((q: string, i: number) => ` ${i + 1}) ${q}`).join("\n")}` : "- latestFeedbackDetails: []",
|
|
1766
1824
|
answerLines ? `- answers:\n${answerLines}` : "- answers: []",
|
|
1767
1825
|
].join("\n");
|
|
1768
1826
|
}
|
|
@@ -1779,7 +1837,6 @@ function buildV2ActionPrompt(context: AgentActionContext) {
|
|
|
1779
1837
|
: [];
|
|
1780
1838
|
return [
|
|
1781
1839
|
`${index + 1}. mine_task`,
|
|
1782
|
-
`- reason: ${item.reason}`,
|
|
1783
1840
|
`- taskId: ${payload.taskId || ""}`,
|
|
1784
1841
|
`- title: ${payload.title || ""}`,
|
|
1785
1842
|
`- boardId: ${payload.boardId || ""}`,
|
|
@@ -1792,7 +1849,7 @@ function buildV2ActionPrompt(context: AgentActionContext) {
|
|
|
1792
1849
|
latestFeedbackDetails.length ? `- latestFeedbackDetails:\n${latestFeedbackDetails.map((q: string, i: number) => ` ${i + 1}) ${q}`).join("\n")}` : "- latestFeedbackDetails: []",
|
|
1793
1850
|
].join("\n");
|
|
1794
1851
|
}
|
|
1795
|
-
return `${index + 1}. ${item.kind}
|
|
1852
|
+
return `${index + 1}. ${item.kind}`;
|
|
1796
1853
|
}).join("\n\n");
|
|
1797
1854
|
|
|
1798
1855
|
return [
|
|
@@ -1807,7 +1864,7 @@ function buildV2ActionPrompt(context: AgentActionContext) {
|
|
|
1807
1864
|
"",
|
|
1808
1865
|
"请输出一个 JSON 对象,且仅输出 JSON,不要任何解释文字。",
|
|
1809
1866
|
"严格格式:",
|
|
1810
|
-
"{\"kind\":\"answer_question|vote_question|mine_task\",\"payload\":{...}
|
|
1867
|
+
"{\"kind\":\"answer_question|vote_question|mine_task\",\"payload\":{...}}",
|
|
1811
1868
|
"",
|
|
1812
1869
|
"规则:",
|
|
1813
1870
|
"1. 只允许从上面可选动作里选 kind。",
|
|
@@ -1815,7 +1872,7 @@ function buildV2ActionPrompt(context: AgentActionContext) {
|
|
|
1815
1872
|
"3. vote_question 时,payload 必须含 questionId、answerId、comment。",
|
|
1816
1873
|
"4. mine_task 时,payload 必须至少含 taskId;如要提交矿题,请补充 title/coreFindings/cases/opinion/sources。",
|
|
1817
1874
|
"5. 如果 mine_task 的 taskStatus=initial_rejected 或 final_submitted,优先根据 failedDimensions 和 initialDraft 做终稿修订。",
|
|
1818
|
-
"6.
|
|
1875
|
+
"6. 不要输出 reason 等额外字段,也不要调用任何工具。",
|
|
1819
1876
|
].filter(Boolean).join("\n");
|
|
1820
1877
|
}
|
|
1821
1878
|
|
|
@@ -1928,7 +1985,7 @@ function buildWindowsAgentCommandScript(input: { agentId: string; sessionId: str
|
|
|
1928
1985
|
"$forumMessage = @'",
|
|
1929
1986
|
safeMessage,
|
|
1930
1987
|
"'@",
|
|
1931
|
-
`openclaw agent --session-id ${quoteForPowerShell(input.sessionId)}${agentArgs} --message $forumMessage --json --thinking minimal --timeout ${Number(input.timeoutSeconds)}`,
|
|
1988
|
+
`openclaw agent --local --session-id ${quoteForPowerShell(input.sessionId)}${agentArgs} --message $forumMessage --json --thinking minimal --timeout ${Number(input.timeoutSeconds)}`,
|
|
1932
1989
|
].join("\n");
|
|
1933
1990
|
}
|
|
1934
1991
|
|
|
@@ -2945,4 +3002,10 @@ async function promiseWithTimeout<T>(promise: Promise<T>, timeoutMs: number): Pr
|
|
|
2945
3002
|
]);
|
|
2946
3003
|
}
|
|
2947
3004
|
|
|
3005
|
+
function resolveAgentCommandTimeoutMs(timeoutSeconds: number, timeoutMs: number) {
|
|
3006
|
+
const cliBudgetMs = Math.max(0, Math.round(Number(timeoutSeconds) || 0)) * 1_000 + 10_000;
|
|
3007
|
+
const externalBudgetMs = Math.max(0, Math.round(Number(timeoutMs) || 0));
|
|
3008
|
+
return Math.max(cliBudgetMs, externalBudgetMs);
|
|
3009
|
+
}
|
|
3010
|
+
|
|
2948
3011
|
export const reporter = new Reporter();
|