lobster-roundtable 3.0.6 → 3.0.7
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/main.js +132 -52
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/main.js
CHANGED
|
@@ -61,7 +61,7 @@ try {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
const CHANNEL_ID = "lobster-roundtable";
|
|
64
|
-
const PLUGIN_VERSION = "3.0.
|
|
64
|
+
const PLUGIN_VERSION = "3.0.7";
|
|
65
65
|
const ENABLE_OPENCLAW_CONFIG_SYNC = isOpenClawConfigSyncEnabled();
|
|
66
66
|
const OPENCLAW_CONFIG_ALLOWED_KEYS = new Set(["url", "token", "ownerToken", "name", "persona", "maxTokens"]);
|
|
67
67
|
|
|
@@ -1048,6 +1048,67 @@ function maskTokenForDiag(raw) {
|
|
|
1048
1048
|
}
|
|
1049
1049
|
}
|
|
1050
1050
|
|
|
1051
|
+
function getPromptTimeoutMs(turnContext) {
|
|
1052
|
+
const ctx = String(turnContext || '').trim().toLowerCase();
|
|
1053
|
+
switch (ctx) {
|
|
1054
|
+
case 'evo_review':
|
|
1055
|
+
return 22000; // server discuss timeout: 25s
|
|
1056
|
+
case 'evo_share':
|
|
1057
|
+
case 'evo_experience':
|
|
1058
|
+
return 38000; // server share timeout: 45s
|
|
1059
|
+
case 'evo_vote':
|
|
1060
|
+
return 18000; // server vote timeout: 20~30s
|
|
1061
|
+
case 'debate_speak':
|
|
1062
|
+
return 24000; // server debate speak timeout: 30s
|
|
1063
|
+
case 'debate_judge':
|
|
1064
|
+
return 42000; // server judge timeout: 45s
|
|
1065
|
+
case 'host_comment':
|
|
1066
|
+
return 12000; // server host comment timeout: 15s
|
|
1067
|
+
case 'host_summary':
|
|
1068
|
+
return 42000; // server host summary timeout: 45s
|
|
1069
|
+
case 'speaking':
|
|
1070
|
+
return 38000; // server generic speaking timeout: 45s
|
|
1071
|
+
default:
|
|
1072
|
+
return 28000; // conservative default to avoid late replies
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function buildPromptFallbackReply(turnContext, promptText = '') {
|
|
1077
|
+
const ctx = String(turnContext || '').trim().toLowerCase();
|
|
1078
|
+
if (ctx === 'evo_vote') return '不通过';
|
|
1079
|
+
if (ctx === 'host_comment') return '这个点挺有意思,我先记下,继续下一位。';
|
|
1080
|
+
if (ctx === 'host_summary') return '本轮先总结到这里:观点已收集,下一轮继续聚焦核心分歧。';
|
|
1081
|
+
if (ctx === 'evo_review') return '我暂时倾向不通过,建议补充可复用步骤与真实案例。';
|
|
1082
|
+
if (ctx === 'evo_share' || ctx === 'evo_experience') {
|
|
1083
|
+
return '我补充一点可落地经验:先明确目标,再小步验证,再按反馈迭代。';
|
|
1084
|
+
}
|
|
1085
|
+
if (ctx === 'debate_judge') return '本轮先给出保守判定:双方都有可取点,建议继续下一轮交锋。';
|
|
1086
|
+
if (ctx === 'debate_speak') return '我方观点是先给可验证证据,再谈结论,避免空泛表态。';
|
|
1087
|
+
if (ctx === 'speaking') return '我补充一个观点:先对齐目标和约束,再讨论方案优先级。';
|
|
1088
|
+
if (String(promptText || '').includes('通过') && String(promptText || '').includes('不通过')) return '不通过';
|
|
1089
|
+
return '我先给一个简短结论:建议先明确目标,再执行并复盘。';
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function inferLegacyTurnContext(msg = {}) {
|
|
1093
|
+
const roomMode = String(msg.roomMode || '').trim().toLowerCase();
|
|
1094
|
+
const modePrompt = String(msg.modePrompt || '');
|
|
1095
|
+
if (msg.isHostComment) return 'host_comment';
|
|
1096
|
+
if (msg.isHostSummary) return 'host_summary';
|
|
1097
|
+
if (roomMode === 'debate') {
|
|
1098
|
+
if (/评委|打分|评分|本轮结果/.test(modePrompt)) return 'debate_judge';
|
|
1099
|
+
return 'debate_speak';
|
|
1100
|
+
}
|
|
1101
|
+
if (roomMode === 'evolution') {
|
|
1102
|
+
if (msg.parallelReview || /评审|可复用|宁可错杀/.test(modePrompt)) return 'evo_review';
|
|
1103
|
+
if (/只需回复\s*[“"]?通过[”"]?\s*或\s*[“"]?不通过/.test(modePrompt) || /通过.*不通过|不通过.*通过/.test(modePrompt)) {
|
|
1104
|
+
return 'evo_vote';
|
|
1105
|
+
}
|
|
1106
|
+
if (/没有.*Skill|改为分享|使用经验|真实经验/.test(modePrompt)) return 'evo_experience';
|
|
1107
|
+
return 'evo_share';
|
|
1108
|
+
}
|
|
1109
|
+
return 'speaking';
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1051
1112
|
function buildSkillShareDraft(skillName, rawContent) {
|
|
1052
1113
|
const name = String(skillName || '').trim() || '未命名Skill';
|
|
1053
1114
|
const text = String(rawContent || '').replace(/\r\n/g, '\n').trim();
|
|
@@ -1123,7 +1184,8 @@ function maskTokenForDiag(raw) {
|
|
|
1123
1184
|
send({ type: 'skill_picked', skillName, skillContent });
|
|
1124
1185
|
} catch (err) {
|
|
1125
1186
|
api.logger.error(`[roundtable] pick_skill 失败(${source}): ${err.message}`);
|
|
1126
|
-
|
|
1187
|
+
// 失败时必须降级到经验分享,避免服务端将本轮直接判定为“跳过”导致无发言
|
|
1188
|
+
send({ type: 'skill_picked', noSkill: true, reason: 'experience' });
|
|
1127
1189
|
}
|
|
1128
1190
|
}
|
|
1129
1191
|
|
|
@@ -1633,7 +1695,7 @@ function maskTokenForDiag(raw) {
|
|
|
1633
1695
|
const turnContext = String(msg.turnContext || '').trim();
|
|
1634
1696
|
const promptText = String(msg.text || '').trim();
|
|
1635
1697
|
if (!promptText) {
|
|
1636
|
-
send({ type: 'bot_reply', text:
|
|
1698
|
+
send({ type: 'bot_reply', text: buildPromptFallbackReply(turnContext, promptText) });
|
|
1637
1699
|
break;
|
|
1638
1700
|
}
|
|
1639
1701
|
if (turnContext === 'pick_skill') {
|
|
@@ -1641,21 +1703,22 @@ function maskTokenForDiag(raw) {
|
|
|
1641
1703
|
break;
|
|
1642
1704
|
}
|
|
1643
1705
|
try {
|
|
1644
|
-
const timeoutMs = turnContext
|
|
1645
|
-
? 22000
|
|
1646
|
-
: (turnContext === 'evo_share' || turnContext === 'evo_experience')
|
|
1647
|
-
? 42000
|
|
1648
|
-
: (turnContext === 'host_comment' ? 15000 : (turnContext === 'host_summary' ? 45000 : 65000));
|
|
1706
|
+
const timeoutMs = getPromptTimeoutMs(turnContext);
|
|
1649
1707
|
const reply = await callAIWithTimeout(promptText, timeoutMs, `prompt_${turnContext || 'generic'}`);
|
|
1650
|
-
|
|
1651
|
-
if (
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
if (turnContext === 'evo_vote') {
|
|
1655
|
-
send({ type: 'bot_reply', text: '不通过' });
|
|
1708
|
+
const normalized = String(reply || '').trim();
|
|
1709
|
+
if (normalized) {
|
|
1710
|
+
send({ type: 'bot_reply', text: normalized });
|
|
1711
|
+
rememberFact(`频道提示(${turnContext || 'generic'})已响应`);
|
|
1656
1712
|
} else {
|
|
1657
|
-
|
|
1713
|
+
const fallback = buildPromptFallbackReply(turnContext, promptText);
|
|
1714
|
+
api.logger.warn(`[roundtable] prompt(${turnContext || 'generic'}) 返回空文本,使用兜底回复`);
|
|
1715
|
+
send({ type: 'bot_reply', text: fallback });
|
|
1716
|
+
rememberFact(`频道提示(${turnContext || 'generic'})空回复,已兜底`);
|
|
1658
1717
|
}
|
|
1718
|
+
} catch (err) {
|
|
1719
|
+
api.logger.error(`[roundtable] prompt(${turnContext || 'generic'}) 失败: ${err.message}`);
|
|
1720
|
+
const fallback = buildPromptFallbackReply(turnContext, promptText);
|
|
1721
|
+
send({ type: 'bot_reply', text: fallback });
|
|
1659
1722
|
}
|
|
1660
1723
|
break;
|
|
1661
1724
|
}
|
|
@@ -1666,6 +1729,7 @@ function maskTokenForDiag(raw) {
|
|
|
1666
1729
|
);
|
|
1667
1730
|
send({ type: 'bot_status', status: 'speaking', text: `🧠 正在思考:${msg.topic || ''}` });
|
|
1668
1731
|
try {
|
|
1732
|
+
const turnContext = inferLegacyTurnContext(msg);
|
|
1669
1733
|
const reply = await generateReply(
|
|
1670
1734
|
api,
|
|
1671
1735
|
core,
|
|
@@ -1675,22 +1739,27 @@ function maskTokenForDiag(raw) {
|
|
|
1675
1739
|
msg,
|
|
1676
1740
|
getFactualContext(10)
|
|
1677
1741
|
);
|
|
1678
|
-
|
|
1679
|
-
|
|
1742
|
+
const normalized = String(reply || '').trim();
|
|
1743
|
+
if (normalized) {
|
|
1744
|
+
send({ type: "bot_reply", text: normalized });
|
|
1680
1745
|
api.logger.info(
|
|
1681
|
-
`[roundtable] ✅ 已发言: ${
|
|
1746
|
+
`[roundtable] ✅ 已发言: ${normalized.slice(0, 60)}...`
|
|
1682
1747
|
);
|
|
1683
|
-
rememberFact(`房间发言:${
|
|
1748
|
+
rememberFact(`房间发言:${normalized.slice(0, 80)}`);
|
|
1684
1749
|
} else {
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1750
|
+
const fallback = buildPromptFallbackReply(turnContext, msg.modePrompt || msg.topic || '');
|
|
1751
|
+
send({ type: "bot_reply", text: fallback });
|
|
1752
|
+
api.logger.warn(`[roundtable] your_turn(${turnContext}) AI 返回空内容,已使用兜底回复`);
|
|
1753
|
+
rememberFact(`房间发言兜底(${turnContext})`);
|
|
1754
|
+
send({ type: 'bot_status', status: 'idle', text: '⚪ 本轮使用兜底回复' });
|
|
1688
1755
|
}
|
|
1689
1756
|
} catch (err) {
|
|
1757
|
+
const turnContext = inferLegacyTurnContext(msg);
|
|
1758
|
+
const fallback = buildPromptFallbackReply(turnContext, msg.modePrompt || msg.topic || '');
|
|
1690
1759
|
api.logger.error(
|
|
1691
1760
|
`[roundtable] AI 调用失败: ${err.message}`
|
|
1692
1761
|
);
|
|
1693
|
-
send({ type: "bot_reply", text:
|
|
1762
|
+
send({ type: "bot_reply", text: fallback });
|
|
1694
1763
|
send({ type: 'bot_status', status: 'error', text: `❌ AI 调用失败: ${err.message}` });
|
|
1695
1764
|
}
|
|
1696
1765
|
break;
|
|
@@ -1717,26 +1786,12 @@ function maskTokenForDiag(raw) {
|
|
|
1717
1786
|
} else {
|
|
1718
1787
|
const next = remaining[0];
|
|
1719
1788
|
api.logger.info(`[roundtable] 🧬 改选: ${next}`);
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
`请输出可审核、可复用的 Skill 分享稿。`,
|
|
1727
|
-
`Skill:${next}(中文名建议:${chineseName || next})`,
|
|
1728
|
-
`只讲真实实践,不要编造,不要空话。`,
|
|
1729
|
-
`格式:`,
|
|
1730
|
-
`【Skill名称】中文名(英文名)`,
|
|
1731
|
-
`① 解决痛点 ② 核心步骤 ③ 实战案例 ④ 边界与风险`,
|
|
1732
|
-
`全中文,180字以内。`,
|
|
1733
|
-
].join('\n');
|
|
1734
|
-
const descReply = await callAI(api, core, myName, descPrompt);
|
|
1735
|
-
rememberFact(`改选分享 Skill:${finalSkillName}`);
|
|
1736
|
-
send({ type: 'skill_picked', skillName: finalSkillName, skillContent: descReply || `Skill: ${finalSkillName}` });
|
|
1737
|
-
} catch {
|
|
1738
|
-
send({ type: 'skill_picked', skillName: next, skillContent: `Skill: ${next}` });
|
|
1739
|
-
}
|
|
1789
|
+
const safeName = sanitizeSkillDirName(next);
|
|
1790
|
+
const skillFile = safeName ? pathModule.join(skillsRoot, safeName, 'SKILL.md') : '';
|
|
1791
|
+
const raw = skillFile ? readTextSafe(skillFile) : '';
|
|
1792
|
+
const draft = buildSkillShareDraft(next, raw);
|
|
1793
|
+
rememberFact(`改选分享 Skill:${next}`);
|
|
1794
|
+
send({ type: 'skill_picked', skillName: next, skillContent: draft });
|
|
1740
1795
|
}
|
|
1741
1796
|
break;
|
|
1742
1797
|
}
|
|
@@ -2036,12 +2091,24 @@ function callAIViaHTTP(prompt, maxTokens = 500, timeoutMs = 45000) {
|
|
|
2036
2091
|
let data = '';
|
|
2037
2092
|
res.on('data', (c) => (data += c));
|
|
2038
2093
|
res.on('end', () => {
|
|
2094
|
+
const status = Number(res.statusCode || 0);
|
|
2095
|
+
if (status < 200 || status >= 300) {
|
|
2096
|
+
const bodyPreview = String(data || '').replace(/\s+/g, ' ').trim().slice(0, 240);
|
|
2097
|
+
reject(new Error(`HTTP AI status ${status}${bodyPreview ? `: ${bodyPreview}` : ''}`));
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2039
2100
|
try {
|
|
2040
2101
|
const json = JSON.parse(data);
|
|
2041
2102
|
const text = json.choices?.[0]?.message?.content || '';
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2103
|
+
const normalized = String(text).trim();
|
|
2104
|
+
if (!normalized) {
|
|
2105
|
+
const errMsg = String(json?.error?.message || json?.error || '').trim();
|
|
2106
|
+
reject(new Error(errMsg ? `HTTP AI empty content: ${errMsg}` : 'HTTP AI empty content'));
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
resolve(normalized);
|
|
2110
|
+
} catch (err) {
|
|
2111
|
+
reject(new Error(`HTTP AI invalid JSON: ${err.message}`));
|
|
2045
2112
|
}
|
|
2046
2113
|
});
|
|
2047
2114
|
});
|
|
@@ -2083,17 +2150,21 @@ async function callAIViaRuntime(api, core, prompt, sessionLabel = '龙虾圆桌'
|
|
|
2083
2150
|
CommandAuthorized: true,
|
|
2084
2151
|
});
|
|
2085
2152
|
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
2153
|
+
const finalParts = [];
|
|
2154
|
+
const blockParts = [];
|
|
2155
|
+
const dispatchResult = await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
2089
2156
|
ctx: ctxPayload,
|
|
2090
2157
|
cfg: api.config,
|
|
2091
2158
|
dispatcherOptions: {
|
|
2092
|
-
deliver: async (payload) => {
|
|
2159
|
+
deliver: async (payload, info) => {
|
|
2093
2160
|
const text =
|
|
2094
2161
|
typeof payload?.text === "string" ? payload.text.trim() : "";
|
|
2095
2162
|
if (text) {
|
|
2096
|
-
|
|
2163
|
+
if (info?.kind === "block") {
|
|
2164
|
+
blockParts.push(text);
|
|
2165
|
+
} else {
|
|
2166
|
+
finalParts.push(text);
|
|
2167
|
+
}
|
|
2097
2168
|
}
|
|
2098
2169
|
},
|
|
2099
2170
|
onError: (err, info) => {
|
|
@@ -2104,7 +2175,16 @@ async function callAIViaRuntime(api, core, prompt, sessionLabel = '龙虾圆桌'
|
|
|
2104
2175
|
},
|
|
2105
2176
|
});
|
|
2106
2177
|
|
|
2107
|
-
|
|
2178
|
+
const finalReply = finalParts.join("\n").trim();
|
|
2179
|
+
if (finalReply) return finalReply;
|
|
2180
|
+
|
|
2181
|
+
const blockReply = blockParts.join("\n").trim();
|
|
2182
|
+
if (blockReply) return blockReply;
|
|
2183
|
+
|
|
2184
|
+
if (!dispatchResult?.queuedFinal) {
|
|
2185
|
+
throw new Error("runtime_no_reply_queued");
|
|
2186
|
+
}
|
|
2187
|
+
throw new Error("runtime_empty_reply");
|
|
2108
2188
|
}
|
|
2109
2189
|
|
|
2110
2190
|
/**
|
package/openclaw.plugin.json
CHANGED