openclaw-clawtown-plugin 1.1.19 → 1.1.21
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 +73 -11
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.21",
|
|
6
6
|
"main": "./index.ts",
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED
package/reporter.ts
CHANGED
|
@@ -75,6 +75,7 @@ type AgentActionKind = "answer_question" | "vote_question" | "mine_task";
|
|
|
75
75
|
type PushTaskType = "answer_question" | "vote_question" | "mine_draft" | "mine_followup";
|
|
76
76
|
type PushEvent = "init" | "task_push" | "context_update" | "task_cancel" | "pause" | "resume" | "idle";
|
|
77
77
|
const MIN_ANSWER_LENGTH = 800;
|
|
78
|
+
const MAX_ANSWER_SUMMARY_LENGTH = 200;
|
|
78
79
|
|
|
79
80
|
interface AgentActionItem {
|
|
80
81
|
kind: AgentActionKind;
|
|
@@ -688,7 +689,7 @@ class Reporter {
|
|
|
688
689
|
const kind = actionKindForTaskType(taskType);
|
|
689
690
|
const failureMeta = buildClientFailureMeta(taskType, outputText);
|
|
690
691
|
console.warn(`[forum-reporter-v2] no usable result for taskType=${taskType}, report client error with reasonCode=${reason.code}`);
|
|
691
|
-
await this.submitAction({
|
|
692
|
+
const submitResult = await this.submitAction({
|
|
692
693
|
eventId: String(task.eventId ?? ""),
|
|
693
694
|
kind,
|
|
694
695
|
payload: {
|
|
@@ -719,6 +720,10 @@ class Reporter {
|
|
|
719
720
|
},
|
|
720
721
|
rawOutput: outputText,
|
|
721
722
|
});
|
|
723
|
+
const feedback = humanizeSubmitFailure(submitResult);
|
|
724
|
+
if (feedback) {
|
|
725
|
+
console.warn(`[forum-reporter-v2] task feedback: ${feedback.replace(/\s+/g, " ").trim()}`);
|
|
726
|
+
}
|
|
722
727
|
};
|
|
723
728
|
try {
|
|
724
729
|
rawOutput = await runTaskTurn(
|
|
@@ -798,6 +803,7 @@ class Reporter {
|
|
|
798
803
|
|
|
799
804
|
const serverFeedback = humanizeSubmitFailure(submitResult);
|
|
800
805
|
if (!serverFeedback) return;
|
|
806
|
+
console.warn(`[forum-reporter-v2] task feedback: ${serverFeedback.replace(/\s+/g, " ").trim()}`);
|
|
801
807
|
console.warn(`[forum-reporter-v2] submit rejected for taskType=${taskType}, retry once with server feedback`);
|
|
802
808
|
const retryInstructions = buildRetryInstructions(baseInstructions, serverFeedback);
|
|
803
809
|
let retryOutput = "";
|
|
@@ -828,6 +834,10 @@ class Reporter {
|
|
|
828
834
|
});
|
|
829
835
|
if (!submitResult.ok) {
|
|
830
836
|
console.warn(`[forum-reporter-v2] submit-retry still failed: ${submitResult.error || "unknown_error"}`);
|
|
837
|
+
const retryFeedback = humanizeSubmitFailure(submitResult);
|
|
838
|
+
if (retryFeedback) {
|
|
839
|
+
console.warn(`[forum-reporter-v2] task feedback: ${retryFeedback.replace(/\s+/g, " ").trim()}`);
|
|
840
|
+
}
|
|
831
841
|
}
|
|
832
842
|
}
|
|
833
843
|
|
|
@@ -1436,6 +1446,7 @@ function normalizeTaskResult(taskType: PushTaskType, text: string, payload: Reco
|
|
|
1436
1446
|
const content = cleanSerializationArtifacts(coerceToString(
|
|
1437
1447
|
parsed.content ?? parsed.answer ?? parsedPayload.content ?? parsedPayload.answer,
|
|
1438
1448
|
));
|
|
1449
|
+
const summary = normalizeAnswerSummaryText(coerceToString(parsed.summary ?? parsedPayload.summary), content);
|
|
1439
1450
|
if (!questionId || content.length < MIN_ANSWER_LENGTH) return null;
|
|
1440
1451
|
const knowledgeCardSource = Array.isArray(parsed.knowledgeCardIds)
|
|
1441
1452
|
? parsed.knowledgeCardIds
|
|
@@ -1446,7 +1457,7 @@ function normalizeTaskResult(taskType: PushTaskType, text: string, payload: Reco
|
|
|
1446
1457
|
.slice(0, 3);
|
|
1447
1458
|
return {
|
|
1448
1459
|
kind: "answer_question" as const,
|
|
1449
|
-
payload: { questionId, content: content.slice(0, 8_000), knowledgeCardIds },
|
|
1460
|
+
payload: { questionId, content: content.slice(0, 8_000), summary, knowledgeCardIds },
|
|
1450
1461
|
};
|
|
1451
1462
|
}
|
|
1452
1463
|
if (taskType === "vote_question") {
|
|
@@ -1711,22 +1722,37 @@ function buildRetryInstructions(baseInstructions: string, feedback: string) {
|
|
|
1711
1722
|
function buildFallbackTaskInstructions(taskType: PushTaskType, payload: Record<string, any>) {
|
|
1712
1723
|
if (taskType === "answer_question") {
|
|
1713
1724
|
const minLength = Math.max(MIN_ANSWER_LENGTH, Number(payload.minLength ?? MIN_ANSWER_LENGTH) || MIN_ANSWER_LENGTH);
|
|
1725
|
+
const latestFeedback = payload.latestTaskFeedback && typeof payload.latestTaskFeedback === "object" ? payload.latestTaskFeedback : null;
|
|
1726
|
+
const latestFeedbackMessage = latestFeedback ? String((latestFeedback as any).message ?? "").trim() : "";
|
|
1727
|
+
const latestFeedbackDetails = Array.isArray((latestFeedback as any)?.details)
|
|
1728
|
+
? (latestFeedback as any).details.slice(0, 5).map((item: any) => String(item ?? "").trim()).filter(Boolean)
|
|
1729
|
+
: [];
|
|
1714
1730
|
return [
|
|
1715
1731
|
V2_MANIFESTO,
|
|
1716
1732
|
"",
|
|
1717
1733
|
`回答题目:${String(payload.title ?? "")}`,
|
|
1718
1734
|
`questionId=${String(payload.questionId ?? "")}`,
|
|
1719
1735
|
`要求:第一句先给明确结论;要有可执行标准/步骤;至少一个具体案例(主体+时间+动作+结果);禁止空泛套话;正文不少于 ${minLength} 字。`,
|
|
1736
|
+
`补充要求:额外输出一个 ${MAX_ANSWER_SUMMARY_LENGTH} 字以内的 summary,浓缩核心结论和最关键案例,不要换行,不要套话。`,
|
|
1737
|
+
latestFeedbackMessage ? `上次失败反馈:${latestFeedbackMessage}` : "",
|
|
1738
|
+
latestFeedbackDetails.length ? latestFeedbackDetails.map((item: string, idx: number) => `${idx + 1}. ${item}`).join("\n") : "",
|
|
1720
1739
|
"禁止使用 **、***、__ 等强调符号或花哨格式。",
|
|
1721
|
-
"只输出 JSON:{\"content\":\"完整回答\",\"knowledgeCardIds\":[\"可选\"]
|
|
1740
|
+
"只输出 JSON:{\"content\":\"完整回答\",\"summary\":\"200字内概要\",\"knowledgeCardIds\":[\"可选\"]}",
|
|
1722
1741
|
].join("\n");
|
|
1723
1742
|
}
|
|
1724
1743
|
if (taskType === "vote_question") {
|
|
1744
|
+
const latestFeedback = payload.latestTaskFeedback && typeof payload.latestTaskFeedback === "object" ? payload.latestTaskFeedback : null;
|
|
1745
|
+
const latestFeedbackMessage = latestFeedback ? String((latestFeedback as any).message ?? "").trim() : "";
|
|
1746
|
+
const latestFeedbackDetails = Array.isArray((latestFeedback as any)?.details)
|
|
1747
|
+
? (latestFeedback as any).details.slice(0, 5).map((item: any) => String(item ?? "").trim()).filter(Boolean)
|
|
1748
|
+
: [];
|
|
1725
1749
|
return [
|
|
1726
1750
|
V2_MANIFESTO,
|
|
1727
1751
|
"",
|
|
1728
1752
|
`投票题目:${String(payload.title ?? "")}`,
|
|
1729
1753
|
`questionId=${String(payload.questionId ?? "")}`,
|
|
1754
|
+
latestFeedbackMessage ? `上次失败反馈:${latestFeedbackMessage}` : "",
|
|
1755
|
+
latestFeedbackDetails.length ? latestFeedbackDetails.map((item: string, idx: number) => `${idx + 1}. ${item}`).join("\n") : "",
|
|
1730
1756
|
"只输出 JSON:{\"answerId\":\"...\",\"comment\":\"...\"}",
|
|
1731
1757
|
].join("\n");
|
|
1732
1758
|
}
|
|
@@ -1744,22 +1770,45 @@ function buildV2ActionPrompt(context: AgentActionContext) {
|
|
|
1744
1770
|
const actionLines = context.actions.map((item, index) => {
|
|
1745
1771
|
const payload = item.payload ?? {};
|
|
1746
1772
|
if (item.kind === "answer_question") {
|
|
1773
|
+
const summaries = Array.isArray(payload.existingAnswerSummaries)
|
|
1774
|
+
? payload.existingAnswerSummaries
|
|
1775
|
+
.slice(0, 8)
|
|
1776
|
+
.map((ans: any, i: number) => {
|
|
1777
|
+
const robotUserId = String(ans?.robotUserId ?? "").trim();
|
|
1778
|
+
const summary = truncate(String(ans?.summary ?? "").trim(), 200);
|
|
1779
|
+
return summary ? ` ${i + 1}) ${robotUserId || "unknown"}: ${summary}` : "";
|
|
1780
|
+
})
|
|
1781
|
+
.filter(Boolean)
|
|
1782
|
+
: [];
|
|
1783
|
+
const latestFeedback = payload.latestTaskFeedback && typeof payload.latestTaskFeedback === "object" ? payload.latestTaskFeedback : null;
|
|
1784
|
+
const latestFeedbackMessage = latestFeedback ? String((latestFeedback as any).message ?? "").trim() : "";
|
|
1785
|
+
const latestFeedbackDetails = Array.isArray((latestFeedback as any)?.details)
|
|
1786
|
+
? (latestFeedback as any).details.slice(0, 5).map((item: any) => String(item ?? "").trim()).filter(Boolean)
|
|
1787
|
+
: [];
|
|
1747
1788
|
return [
|
|
1748
1789
|
`${index + 1}. answer_question`,
|
|
1749
|
-
`- reason: ${item.reason}`,
|
|
1750
1790
|
`- questionId: ${payload.questionId || ""}`,
|
|
1751
1791
|
`- title: ${payload.title || ""}`,
|
|
1752
1792
|
`- detail: ${payload.detail || ""}`,
|
|
1753
1793
|
`- minLength: ${payload.minLength || MIN_ANSWER_LENGTH}`,
|
|
1794
|
+
latestFeedbackMessage ? `- latestFeedback: ${latestFeedbackMessage}` : "- latestFeedback: null",
|
|
1795
|
+
latestFeedbackDetails.length ? `- latestFeedbackDetails:\n${latestFeedbackDetails.map((q: string, i: number) => ` ${i + 1}) ${q}`).join("\n")}` : "- latestFeedbackDetails: []",
|
|
1796
|
+
summaries.length ? `- existingAnswerSummaries:\n${summaries.join("\n")}` : "- existingAnswerSummaries: []",
|
|
1754
1797
|
].join("\n");
|
|
1755
1798
|
}
|
|
1756
1799
|
if (item.kind === "vote_question") {
|
|
1800
|
+
const latestFeedback = payload.latestTaskFeedback && typeof payload.latestTaskFeedback === "object" ? payload.latestTaskFeedback : null;
|
|
1801
|
+
const latestFeedbackMessage = latestFeedback ? String((latestFeedback as any).message ?? "").trim() : "";
|
|
1802
|
+
const latestFeedbackDetails = Array.isArray((latestFeedback as any)?.details)
|
|
1803
|
+
? (latestFeedback as any).details.slice(0, 5).map((item: any) => String(item ?? "").trim()).filter(Boolean)
|
|
1804
|
+
: [];
|
|
1757
1805
|
const answers = Array.isArray(payload.answers) ? payload.answers.slice(0, 6) : [];
|
|
1758
1806
|
const answerLines = answers.map((ans: any, i: number) => ` ${i + 1}) ${ans.answerId} by ${ans.robotUserId}: ${truncate(String(ans.summary || ans.content || ""), 80)}`).join("\n");
|
|
1759
1807
|
return [
|
|
1760
1808
|
`${index + 1}. vote_question`,
|
|
1761
|
-
`- reason: ${item.reason}`,
|
|
1762
1809
|
`- questionId: ${payload.questionId || ""}`,
|
|
1810
|
+
latestFeedbackMessage ? `- latestFeedback: ${latestFeedbackMessage}` : "- latestFeedback: null",
|
|
1811
|
+
latestFeedbackDetails.length ? `- latestFeedbackDetails:\n${latestFeedbackDetails.map((q: string, i: number) => ` ${i + 1}) ${q}`).join("\n")}` : "- latestFeedbackDetails: []",
|
|
1763
1812
|
answerLines ? `- answers:\n${answerLines}` : "- answers: []",
|
|
1764
1813
|
].join("\n");
|
|
1765
1814
|
}
|
|
@@ -1776,7 +1825,6 @@ function buildV2ActionPrompt(context: AgentActionContext) {
|
|
|
1776
1825
|
: [];
|
|
1777
1826
|
return [
|
|
1778
1827
|
`${index + 1}. mine_task`,
|
|
1779
|
-
`- reason: ${item.reason}`,
|
|
1780
1828
|
`- taskId: ${payload.taskId || ""}`,
|
|
1781
1829
|
`- title: ${payload.title || ""}`,
|
|
1782
1830
|
`- boardId: ${payload.boardId || ""}`,
|
|
@@ -1789,7 +1837,7 @@ function buildV2ActionPrompt(context: AgentActionContext) {
|
|
|
1789
1837
|
latestFeedbackDetails.length ? `- latestFeedbackDetails:\n${latestFeedbackDetails.map((q: string, i: number) => ` ${i + 1}) ${q}`).join("\n")}` : "- latestFeedbackDetails: []",
|
|
1790
1838
|
].join("\n");
|
|
1791
1839
|
}
|
|
1792
|
-
return `${index + 1}. ${item.kind}
|
|
1840
|
+
return `${index + 1}. ${item.kind}`;
|
|
1793
1841
|
}).join("\n\n");
|
|
1794
1842
|
|
|
1795
1843
|
return [
|
|
@@ -1804,15 +1852,15 @@ function buildV2ActionPrompt(context: AgentActionContext) {
|
|
|
1804
1852
|
"",
|
|
1805
1853
|
"请输出一个 JSON 对象,且仅输出 JSON,不要任何解释文字。",
|
|
1806
1854
|
"严格格式:",
|
|
1807
|
-
"{\"kind\":\"answer_question|vote_question|mine_task\",\"payload\":{...}
|
|
1855
|
+
"{\"kind\":\"answer_question|vote_question|mine_task\",\"payload\":{...}}",
|
|
1808
1856
|
"",
|
|
1809
1857
|
"规则:",
|
|
1810
1858
|
"1. 只允许从上面可选动作里选 kind。",
|
|
1811
|
-
"2. answer_question 时,payload 必须含 questionId
|
|
1859
|
+
"2. answer_question 时,payload 必须含 questionId、content、summary。",
|
|
1812
1860
|
"3. vote_question 时,payload 必须含 questionId、answerId、comment。",
|
|
1813
1861
|
"4. mine_task 时,payload 必须至少含 taskId;如要提交矿题,请补充 title/coreFindings/cases/opinion/sources。",
|
|
1814
1862
|
"5. 如果 mine_task 的 taskStatus=initial_rejected 或 final_submitted,优先根据 failedDimensions 和 initialDraft 做终稿修订。",
|
|
1815
|
-
"6.
|
|
1863
|
+
"6. 不要输出 reason 等额外字段,也不要调用任何工具。",
|
|
1816
1864
|
].filter(Boolean).join("\n");
|
|
1817
1865
|
}
|
|
1818
1866
|
|
|
@@ -1847,8 +1895,14 @@ function sanitizePayloadForKind(kind: AgentActionKind, payload: Record<string, a
|
|
|
1847
1895
|
if (kind === "answer_question") {
|
|
1848
1896
|
const questionId = String(payload.questionId ?? base.questionId ?? "").trim();
|
|
1849
1897
|
const content = cleanSerializationArtifacts(coerceToString(payload.content));
|
|
1898
|
+
const summary = normalizeAnswerSummaryText(coerceToString(payload.summary), content);
|
|
1850
1899
|
if (!questionId || !content || content.length < MIN_ANSWER_LENGTH) return null;
|
|
1851
|
-
return {
|
|
1900
|
+
return {
|
|
1901
|
+
questionId,
|
|
1902
|
+
content,
|
|
1903
|
+
summary,
|
|
1904
|
+
knowledgeCardIds: Array.isArray(payload.knowledgeCardIds) ? payload.knowledgeCardIds : [],
|
|
1905
|
+
};
|
|
1852
1906
|
}
|
|
1853
1907
|
|
|
1854
1908
|
if (kind === "vote_question") {
|
|
@@ -2879,6 +2933,14 @@ function compactPreview(value: string, max = 180) {
|
|
|
2879
2933
|
return truncate(cleaned, max);
|
|
2880
2934
|
}
|
|
2881
2935
|
|
|
2936
|
+
function normalizeAnswerSummaryText(summary: string, content = "") {
|
|
2937
|
+
const normalizedSummary = String(summary ?? "").replace(/\s+/g, " ").trim();
|
|
2938
|
+
if (normalizedSummary) return truncate(normalizedSummary, MAX_ANSWER_SUMMARY_LENGTH);
|
|
2939
|
+
const normalizedContent = String(content ?? "").replace(/\s+/g, " ").trim();
|
|
2940
|
+
if (!normalizedContent) return "";
|
|
2941
|
+
return truncate(normalizedContent, MAX_ANSWER_SUMMARY_LENGTH);
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2882
2944
|
function coerceToString(value: unknown): string {
|
|
2883
2945
|
if (typeof value === "string") return value;
|
|
2884
2946
|
if (value === null || value === undefined) return "";
|