lobster-roundtable 3.0.8 → 3.0.9
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 +40 -324
- package/openclaw.plugin.json +2 -2
- package/package.json +2 -2
package/main.js
CHANGED
|
@@ -19,6 +19,8 @@ const cryptoModule = require("crypto");
|
|
|
19
19
|
const {
|
|
20
20
|
getOpenClawDir,
|
|
21
21
|
isOpenClawConfigSyncEnabled,
|
|
22
|
+
getOpenClawApiPort,
|
|
23
|
+
getOpenClawApiToken,
|
|
22
24
|
} = require("./src/env.js");
|
|
23
25
|
const { readTextSafe, parseJsonFileFlexible } = require("./src/fileio.js");
|
|
24
26
|
// Node.js 原生 HTTP 请求工具(避免依赖外部命令)
|
|
@@ -59,7 +61,7 @@ try {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
const CHANNEL_ID = "lobster-roundtable";
|
|
62
|
-
const PLUGIN_VERSION = "3.0.
|
|
64
|
+
const PLUGIN_VERSION = "3.0.9";
|
|
63
65
|
const ENABLE_OPENCLAW_CONFIG_SYNC = isOpenClawConfigSyncEnabled();
|
|
64
66
|
const OPENCLAW_CONFIG_ALLOWED_KEYS = new Set(["url", "token", "ownerToken", "name", "persona", "maxTokens"]);
|
|
65
67
|
|
|
@@ -552,17 +554,17 @@ function startBot(api, core, cfg, wsUrl, token, persona, maxTokens, tokenCacheFi
|
|
|
552
554
|
} catch { }
|
|
553
555
|
}
|
|
554
556
|
|
|
555
|
-
function safeDiagText(v, max = 280) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
557
|
+
function safeDiagText(v, max = 280) {
|
|
558
|
+
const s = String(v || "").trim();
|
|
559
|
+
return s.length > max ? s.slice(0, max) : s;
|
|
560
|
+
}
|
|
559
561
|
|
|
560
|
-
function maskTokenForDiag(raw) {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
}
|
|
562
|
+
function maskTokenForDiag(raw) {
|
|
563
|
+
const s = String(raw || "").trim();
|
|
564
|
+
if (!s) return "";
|
|
565
|
+
if (s.length <= 8) return s;
|
|
566
|
+
return `${s.slice(0, 6)}...${s.slice(-4)}`;
|
|
567
|
+
}
|
|
566
568
|
|
|
567
569
|
function reportDiag(event, payload = {}, throttleMs = 8000) {
|
|
568
570
|
if (!event) return;
|
|
@@ -1038,12 +1040,12 @@ function maskTokenForDiag(raw) {
|
|
|
1038
1040
|
return msg.includes('timeout');
|
|
1039
1041
|
}
|
|
1040
1042
|
|
|
1041
|
-
async function callAIWithTimeout(prompt, timeoutMs = 30000, tag = 'callAI'
|
|
1043
|
+
async function callAIWithTimeout(prompt, timeoutMs = 30000, tag = 'callAI') {
|
|
1042
1044
|
const ms = Math.max(5000, parseInt(timeoutMs, 10) || 30000);
|
|
1043
1045
|
let timeoutId = null;
|
|
1044
1046
|
try {
|
|
1045
1047
|
return await Promise.race([
|
|
1046
|
-
callAI(api, core, myName, prompt
|
|
1048
|
+
callAI(api, core, myName, prompt),
|
|
1047
1049
|
new Promise((_, reject) => {
|
|
1048
1050
|
timeoutId = setTimeout(() => reject(new Error(`${tag}_timeout_${ms}`)), ms);
|
|
1049
1051
|
}),
|
|
@@ -1201,12 +1203,7 @@ function maskTokenForDiag(raw) {
|
|
|
1201
1203
|
`SKILL_NAME: 你要分享的技能名`,
|
|
1202
1204
|
`SKILL_DESC: 用中文给出可审核、可复用的分享稿(至少140字,包含痛点/步骤/案例/风险,必须有“输入->动作->结果”链路)`,
|
|
1203
1205
|
].join('\n');
|
|
1204
|
-
const reply = String(await callAIWithTimeout(oneShotPrompt, 28000, 'pick_skill_one_shot'
|
|
1205
|
-
turnContext: 'pick_skill',
|
|
1206
|
-
roomMode: 'evolution',
|
|
1207
|
-
roomId: currentRoomId || '',
|
|
1208
|
-
senderId: 'evo-room',
|
|
1209
|
-
}) || '').trim();
|
|
1206
|
+
const reply = String(await callAIWithTimeout(oneShotPrompt, 28000, 'pick_skill_one_shot') || '').trim();
|
|
1210
1207
|
if (!reply || (/NONE/i.test(reply) && !/SKILL_NAME/i.test(reply))) {
|
|
1211
1208
|
api.logger.info(`[roundtable] 🧬 ${source} 兜底:无可分享技能,降级经验分享`);
|
|
1212
1209
|
send({ type: 'skill_picked', noSkill: true, reason: 'experience' });
|
|
@@ -1731,11 +1728,7 @@ function maskTokenForDiag(raw) {
|
|
|
1731
1728
|
case "prompt": {
|
|
1732
1729
|
const turnContext = String(msg.turnContext || '').trim();
|
|
1733
1730
|
const promptText = String(msg.text || '').trim();
|
|
1734
|
-
|
|
1735
|
-
turnContext,
|
|
1736
|
-
roomMode: currentRoomMode || String(msg.roomMode || ''),
|
|
1737
|
-
roomId: currentRoomId || String(msg.roomId || ''),
|
|
1738
|
-
};
|
|
1731
|
+
|
|
1739
1732
|
if (!promptText) {
|
|
1740
1733
|
const syntheticPrompt = [
|
|
1741
1734
|
`你在龙虾房间中,收到一个缺失正文的阶段提示(turnContext=${turnContext || 'generic'})。`,
|
|
@@ -1746,8 +1739,7 @@ function maskTokenForDiag(raw) {
|
|
|
1746
1739
|
const retry = String(await callAIWithTimeout(
|
|
1747
1740
|
syntheticPrompt,
|
|
1748
1741
|
Math.max(7000, Math.min(12000, getPromptTimeoutMs(turnContext))),
|
|
1749
|
-
`prompt_empty_rebuild_${turnContext || 'generic'}
|
|
1750
|
-
routeOptions
|
|
1742
|
+
`prompt_empty_rebuild_${turnContext || 'generic'}`
|
|
1751
1743
|
) || '').trim();
|
|
1752
1744
|
if (retry) {
|
|
1753
1745
|
send({ type: 'bot_reply', text: retry });
|
|
@@ -1782,8 +1774,7 @@ function maskTokenForDiag(raw) {
|
|
|
1782
1774
|
const reply = await callAIWithTimeout(
|
|
1783
1775
|
promptText,
|
|
1784
1776
|
timeoutMs,
|
|
1785
|
-
`prompt_${turnContext || 'generic'}
|
|
1786
|
-
routeOptions
|
|
1777
|
+
`prompt_${turnContext || 'generic'}`
|
|
1787
1778
|
);
|
|
1788
1779
|
const normalized = String(reply || '').trim();
|
|
1789
1780
|
if (normalized) {
|
|
@@ -1795,8 +1786,7 @@ function maskTokenForDiag(raw) {
|
|
|
1795
1786
|
retried = String(await callAIWithTimeout(
|
|
1796
1787
|
promptText,
|
|
1797
1788
|
Math.max(6000, Math.min(12000, Math.floor(timeoutMs * 0.55))),
|
|
1798
|
-
`prompt_retry_${turnContext || 'generic'}
|
|
1799
|
-
routeOptions
|
|
1789
|
+
`prompt_retry_${turnContext || 'generic'}`
|
|
1800
1790
|
) || '').trim();
|
|
1801
1791
|
} catch { }
|
|
1802
1792
|
if (retried) {
|
|
@@ -1826,8 +1816,7 @@ function maskTokenForDiag(raw) {
|
|
|
1826
1816
|
recovered = String(await callAIWithTimeout(
|
|
1827
1817
|
promptText,
|
|
1828
1818
|
Math.max(6000, Math.min(14000, Math.floor(timeoutMs * 0.6))),
|
|
1829
|
-
`prompt_timeout_retry_${turnContext || 'generic'}
|
|
1830
|
-
routeOptions
|
|
1819
|
+
`prompt_timeout_retry_${turnContext || 'generic'}`
|
|
1831
1820
|
) || '').trim();
|
|
1832
1821
|
} catch { }
|
|
1833
1822
|
}
|
|
@@ -1972,12 +1961,7 @@ function maskTokenForDiag(raw) {
|
|
|
1972
1961
|
`任一条件不满足 -> 不通过。`,
|
|
1973
1962
|
`只需回复 "通过" 或 "不通过",不要回复其他内容。`,
|
|
1974
1963
|
].join("\n");
|
|
1975
|
-
const voteReply = await callAI(api, core, myName, votePrompt
|
|
1976
|
-
turnContext: 'evo_vote',
|
|
1977
|
-
roomMode: 'evolution',
|
|
1978
|
-
roomId: currentRoomId || '',
|
|
1979
|
-
senderId: 'evo-room',
|
|
1980
|
-
});
|
|
1964
|
+
const voteReply = await callAI(api, core, myName, votePrompt);
|
|
1981
1965
|
const decision = String(voteReply || '').trim();
|
|
1982
1966
|
const rejected = /不通过|否决|拒绝|不建议/.test(decision);
|
|
1983
1967
|
const approve = !rejected && /通过/.test(decision);
|
|
@@ -2229,21 +2213,20 @@ function parseContext(context) {
|
|
|
2229
2213
|
function resolveGatewayHttpOptions(api) {
|
|
2230
2214
|
const cfg = (api && typeof api.config === "object" && api.config) ? api.config : {};
|
|
2231
2215
|
const apiCfg = (cfg.api && typeof cfg.api === "object") ? cfg.api : {};
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
const
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
};
|
|
2216
|
+
|
|
2217
|
+
// 端口:环境变量 > config.api.port > config.gateway.port > config.listen.port > 默认 18789
|
|
2218
|
+
const envPort = getOpenClawApiPort();
|
|
2219
|
+
const cfgPort = parseInt(apiCfg.port || cfg.gateway?.port || cfg.listen?.port, 10);
|
|
2220
|
+
const port = (envPort !== 18789) ? envPort
|
|
2221
|
+
: (Number.isFinite(cfgPort) && cfgPort > 0) ? cfgPort
|
|
2222
|
+
: 18789;
|
|
2223
|
+
|
|
2224
|
+
// Token:环境变量 > config.api.token
|
|
2225
|
+
const envToken = getOpenClawApiToken();
|
|
2226
|
+
const cfgToken = String(apiCfg.token || "").trim();
|
|
2227
|
+
const token = envToken || cfgToken;
|
|
2228
|
+
|
|
2229
|
+
return { port, token };
|
|
2247
2230
|
}
|
|
2248
2231
|
|
|
2249
2232
|
/**
|
|
@@ -2309,199 +2292,13 @@ function callAIViaHTTP(prompt, maxTokens = 500, timeoutMs = 45000, httpOptions =
|
|
|
2309
2292
|
});
|
|
2310
2293
|
}
|
|
2311
2294
|
|
|
2312
|
-
function pushRuntimeRouteCandidate(list, candidate) {
|
|
2313
|
-
if (!candidate) return;
|
|
2314
|
-
const sessionKey = String(candidate.sessionKey || '').trim();
|
|
2315
|
-
if (!sessionKey) return;
|
|
2316
|
-
const chatType = String(candidate.chatType || 'direct').trim().toLowerCase() === 'group' ? 'group' : 'direct';
|
|
2317
|
-
const peerId = String(candidate.peerId || '').trim();
|
|
2318
|
-
const dedupeKey = `${sessionKey}|${chatType}|${peerId}`;
|
|
2319
|
-
if (list.some((x) => x.__k === dedupeKey)) return;
|
|
2320
|
-
list.push({ ...candidate, chatType, peerId, __k: dedupeKey });
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2323
|
-
function resolveRuntimeRouteCandidates(api, core, senderId, options = {}) {
|
|
2324
|
-
const candidates = [];
|
|
2325
|
-
const resolver = core?.channel?.routing?.resolveAgentRoute;
|
|
2326
|
-
const accountId = String(api?.accountId || "default");
|
|
2327
|
-
const sender = String(senderId || "roundtable-host").trim() || "roundtable-host";
|
|
2328
|
-
const roomId = String(options.roomId || '').trim();
|
|
2329
|
-
const roomMode = String(options.roomMode || '').trim().toLowerCase();
|
|
2330
|
-
|
|
2331
|
-
const peerCandidates = [];
|
|
2332
|
-
if (roomId) peerCandidates.push({ kind: "group", id: roomId, chatType: "group" });
|
|
2333
|
-
peerCandidates.push({ kind: "direct", id: sender, chatType: "direct" });
|
|
2334
|
-
peerCandidates.push({ kind: "group", id: sender, chatType: "group" });
|
|
2335
|
-
if (roomMode) peerCandidates.push({ kind: "group", id: `${CHANNEL_ID}:${roomMode}`, chatType: "group" });
|
|
2336
|
-
|
|
2337
|
-
if (typeof resolver === "function") {
|
|
2338
|
-
for (const peer of peerCandidates) {
|
|
2339
|
-
try {
|
|
2340
|
-
const route = resolver({
|
|
2341
|
-
cfg: api?.config,
|
|
2342
|
-
channel: CHANNEL_ID,
|
|
2343
|
-
accountId,
|
|
2344
|
-
peer: { kind: peer.kind, id: peer.id },
|
|
2345
|
-
});
|
|
2346
|
-
pushRuntimeRouteCandidate(candidates, {
|
|
2347
|
-
source: "resolver",
|
|
2348
|
-
sessionKey: route?.sessionKey,
|
|
2349
|
-
accountId: String(route?.accountId || accountId),
|
|
2350
|
-
chatType: peer.chatType,
|
|
2351
|
-
peerId: peer.id,
|
|
2352
|
-
});
|
|
2353
|
-
} catch (err) {
|
|
2354
|
-
api?.logger?.warn?.(
|
|
2355
|
-
`[roundtable] resolveAgentRoute(${peer.kind}:${peer.id}) 失败: ${err?.message || err}`
|
|
2356
|
-
);
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
if (roomId) {
|
|
2362
|
-
pushRuntimeRouteCandidate(candidates, {
|
|
2363
|
-
source: "fallback",
|
|
2364
|
-
sessionKey: `roundtable:room:${roomId}`,
|
|
2365
|
-
accountId,
|
|
2366
|
-
chatType: "group",
|
|
2367
|
-
peerId: roomId,
|
|
2368
|
-
});
|
|
2369
|
-
}
|
|
2370
|
-
pushRuntimeRouteCandidate(candidates, {
|
|
2371
|
-
source: "fallback",
|
|
2372
|
-
sessionKey: `roundtable:${sender}`,
|
|
2373
|
-
accountId,
|
|
2374
|
-
chatType: "direct",
|
|
2375
|
-
peerId: sender,
|
|
2376
|
-
});
|
|
2377
|
-
|
|
2378
|
-
return candidates.map(({ __k, ...x }) => x);
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
|
-
async function dispatchRuntimeOnce(api, core, prompt, sessionLabel, senderName, senderId, routeCandidate, attemptIndex = 0) {
|
|
2382
|
-
const sessionKey = String(routeCandidate?.sessionKey || `roundtable:${senderId}`);
|
|
2383
|
-
const accountId = String(routeCandidate?.accountId || api?.accountId || "default");
|
|
2384
|
-
const chatType = String(routeCandidate?.chatType || 'direct').toLowerCase() === 'group' ? 'group' : 'direct';
|
|
2385
|
-
const peerId = String(routeCandidate?.peerId || senderId || 'roundtable-host');
|
|
2386
|
-
const timestamp = Date.now();
|
|
2387
|
-
const toAddress = chatType === 'group'
|
|
2388
|
-
? `${CHANNEL_ID}:group:${peerId}`
|
|
2389
|
-
: `${CHANNEL_ID}:bot:${peerId}`;
|
|
2390
|
-
|
|
2391
|
-
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
2392
|
-
Body: prompt,
|
|
2393
|
-
BodyForAgent: prompt,
|
|
2394
|
-
RawBody: prompt,
|
|
2395
|
-
CommandBody: prompt,
|
|
2396
|
-
From: `${CHANNEL_ID}:server`,
|
|
2397
|
-
To: toAddress,
|
|
2398
|
-
SessionKey: sessionKey,
|
|
2399
|
-
AccountId: accountId,
|
|
2400
|
-
ChatType: chatType,
|
|
2401
|
-
ConversationLabel: sessionLabel,
|
|
2402
|
-
SenderName: senderName,
|
|
2403
|
-
SenderId: senderId,
|
|
2404
|
-
GroupSubject: chatType === 'group' ? peerId : undefined,
|
|
2405
|
-
Provider: CHANNEL_ID,
|
|
2406
|
-
Surface: CHANNEL_ID,
|
|
2407
|
-
MessageSid: `rt-${timestamp}-${attemptIndex}`,
|
|
2408
|
-
Timestamp: timestamp,
|
|
2409
|
-
OriginatingChannel: CHANNEL_ID,
|
|
2410
|
-
OriginatingTo: toAddress,
|
|
2411
|
-
CommandAuthorized: true,
|
|
2412
|
-
});
|
|
2413
|
-
|
|
2414
|
-
const finalParts = [];
|
|
2415
|
-
const blockParts = [];
|
|
2416
|
-
const dispatchResult = await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
2417
|
-
ctx: ctxPayload,
|
|
2418
|
-
cfg: api.config,
|
|
2419
|
-
dispatcherOptions: {
|
|
2420
|
-
deliver: async (payload, info) => {
|
|
2421
|
-
const text = typeof payload?.text === "string" ? payload.text.trim() : "";
|
|
2422
|
-
if (!text) return;
|
|
2423
|
-
if (info?.kind === "block") blockParts.push(text);
|
|
2424
|
-
else finalParts.push(text);
|
|
2425
|
-
},
|
|
2426
|
-
onError: (err, info) => {
|
|
2427
|
-
api.logger.error(`[roundtable] dispatch ${info.kind} 失败: ${String(err)}`);
|
|
2428
|
-
},
|
|
2429
|
-
},
|
|
2430
|
-
});
|
|
2431
|
-
|
|
2432
|
-
const finalReply = finalParts.join("\n").trim();
|
|
2433
|
-
if (finalReply) return finalReply;
|
|
2434
|
-
|
|
2435
|
-
const blockReply = blockParts.join("\n").trim();
|
|
2436
|
-
if (blockReply) return blockReply;
|
|
2437
|
-
|
|
2438
|
-
if (!dispatchResult?.queuedFinal) {
|
|
2439
|
-
const counts = dispatchResult?.counts ? JSON.stringify(dispatchResult.counts) : "{}";
|
|
2440
|
-
throw new Error(`runtime_no_reply_queued counts=${counts}`);
|
|
2441
|
-
}
|
|
2442
|
-
throw new Error("runtime_empty_reply queuedFinal=true");
|
|
2443
|
-
}
|
|
2444
|
-
|
|
2445
|
-
/**
|
|
2446
|
-
* 通过 runtime 内部管线调 AI(优先路径,在支持的 OpenClaw 版本上使用)
|
|
2447
|
-
*/
|
|
2448
|
-
async function callAIViaRuntime(
|
|
2449
|
-
api,
|
|
2450
|
-
core,
|
|
2451
|
-
prompt,
|
|
2452
|
-
sessionLabel = '龙虾圆桌',
|
|
2453
|
-
senderName = '圆桌主持人',
|
|
2454
|
-
senderId = 'roundtable-host',
|
|
2455
|
-
options = {}
|
|
2456
|
-
) {
|
|
2457
|
-
const routeCandidates = resolveRuntimeRouteCandidates(api, core, senderId, options);
|
|
2458
|
-
const failures = [];
|
|
2459
|
-
|
|
2460
|
-
for (let i = 0; i < routeCandidates.length; i++) {
|
|
2461
|
-
const candidate = routeCandidates[i];
|
|
2462
|
-
try {
|
|
2463
|
-
const reply = await dispatchRuntimeOnce(
|
|
2464
|
-
api,
|
|
2465
|
-
core,
|
|
2466
|
-
prompt,
|
|
2467
|
-
sessionLabel,
|
|
2468
|
-
senderName,
|
|
2469
|
-
senderId,
|
|
2470
|
-
candidate,
|
|
2471
|
-
i + 1
|
|
2472
|
-
);
|
|
2473
|
-
if (i > 0) {
|
|
2474
|
-
const diag = (api && typeof api.__rtReportDiag === 'function') ? api.__rtReportDiag : null;
|
|
2475
|
-
diag?.("runtime_route_retry_success", {
|
|
2476
|
-
level: "warn",
|
|
2477
|
-
message: `runtime recovered on candidate #${i + 1}`,
|
|
2478
|
-
detail: {
|
|
2479
|
-
attempt: i + 1,
|
|
2480
|
-
source: candidate.source || 'unknown',
|
|
2481
|
-
chatType: candidate.chatType,
|
|
2482
|
-
peerId: candidate.peerId,
|
|
2483
|
-
},
|
|
2484
|
-
}, 1200);
|
|
2485
|
-
}
|
|
2486
|
-
return reply;
|
|
2487
|
-
} catch (err) {
|
|
2488
|
-
const reason = String(err?.message || err);
|
|
2489
|
-
failures.push(`#${i + 1}(${candidate.chatType}:${candidate.peerId || 'na'}): ${reason}`);
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
|
|
2493
|
-
throw new Error(`runtime_all_candidates_failed ${failures.join(' | ')}`);
|
|
2494
|
-
}
|
|
2495
|
-
|
|
2496
2295
|
/**
|
|
2497
|
-
* 核心 AI
|
|
2296
|
+
* 核心 AI 调用:直接通过 Gateway HTTP API (/v1/chat/completions) 调用 LLM
|
|
2498
2297
|
* 构造圆桌讨论的完整 prompt,让 Gateway 用用户配置的 AI 模型生成回复
|
|
2499
2298
|
*/
|
|
2500
2299
|
async function generateReply(api, core, myName, persona, maxTokens, msg, factualContext = '') {
|
|
2501
|
-
// 兼容服务器字段:可能是 msg.history 或 msg.context
|
|
2502
2300
|
const rawHistory = msg.history || parseContext(msg.context);
|
|
2503
2301
|
let systemPrompt = buildSystemPrompt(myName, persona, msg.topic, msg.roomMode);
|
|
2504
|
-
// 融合服务器下发的模式/角色提示(辩论正反方、进化评审等)
|
|
2505
2302
|
if (msg.modePrompt) {
|
|
2506
2303
|
systemPrompt += "\n\n" + msg.modePrompt;
|
|
2507
2304
|
}
|
|
@@ -2511,95 +2308,14 @@ async function generateReply(api, core, myName, persona, maxTokens, msg, factual
|
|
|
2511
2308
|
: '';
|
|
2512
2309
|
|
|
2513
2310
|
const fullPrompt = `${systemPrompt}${memoryBlock}\n\n---\n\n${userMessage}`;
|
|
2514
|
-
|
|
2515
|
-
// 优先 runtime API → 降级 HTTP API
|
|
2516
|
-
const hasRuntime = !!(core?.channel?.reply?.finalizeInboundContext &&
|
|
2517
|
-
core?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher);
|
|
2518
|
-
|
|
2519
|
-
if (hasRuntime) {
|
|
2520
|
-
try {
|
|
2521
|
-
return await callAIViaRuntime(
|
|
2522
|
-
api,
|
|
2523
|
-
core,
|
|
2524
|
-
fullPrompt,
|
|
2525
|
-
'龙虾圆桌',
|
|
2526
|
-
'圆桌主持人',
|
|
2527
|
-
'roundtable-host',
|
|
2528
|
-
{
|
|
2529
|
-
roomMode: String(msg?.roomMode || ''),
|
|
2530
|
-
roomId: String(msg?.roomId || msg?.room?.id || ''),
|
|
2531
|
-
}
|
|
2532
|
-
);
|
|
2533
|
-
} catch (err) {
|
|
2534
|
-
api.logger.warn(`[roundtable] runtime API 调用失败,降级 HTTP: ${err.message}`);
|
|
2535
|
-
const diag = (api && typeof api.__rtReportDiag === 'function') ? api.__rtReportDiag : null;
|
|
2536
|
-
diag?.("runtime_to_http_fallback", {
|
|
2537
|
-
level: "warn",
|
|
2538
|
-
message: `generate_reply runtime failed: ${err.message}`,
|
|
2539
|
-
reason: String(err.message || err),
|
|
2540
|
-
detail: {
|
|
2541
|
-
phase: "generate_reply",
|
|
2542
|
-
roomMode: String(msg?.roomMode || ''),
|
|
2543
|
-
topicLen: String(msg?.topic || '').length,
|
|
2544
|
-
},
|
|
2545
|
-
}, 1200);
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
2311
|
return await callAIViaHTTP(fullPrompt, maxTokens || 500, 65000, resolveGatewayHttpOptions(api));
|
|
2550
2312
|
}
|
|
2551
2313
|
|
|
2552
2314
|
/**
|
|
2553
2315
|
* 精简版 AI 调用:给一个 prompt,拿一个回复
|
|
2554
|
-
* 用于进化室选 Skill
|
|
2555
|
-
*
|
|
2316
|
+
* 用于进化室选 Skill、评审、投票等不需要完整对话上下文的场景
|
|
2317
|
+
* 直接通过 Gateway HTTP API 调用
|
|
2556
2318
|
*/
|
|
2557
|
-
async function callAI(api, core, myName, prompt
|
|
2558
|
-
const turnContext = String(options.turnContext || '').trim().toLowerCase();
|
|
2559
|
-
const senderId = (() => {
|
|
2560
|
-
const explicit = String(options.senderId || '').trim();
|
|
2561
|
-
if (explicit) return explicit;
|
|
2562
|
-
if (turnContext === 'pick_skill' || turnContext.startsWith('evo_')) return 'evo-room';
|
|
2563
|
-
if (turnContext.startsWith('debate_')) return 'debate-room';
|
|
2564
|
-
if (turnContext.startsWith('host_')) return 'roundtable-host';
|
|
2565
|
-
return 'roundtable-host';
|
|
2566
|
-
})();
|
|
2567
|
-
const sessionLabel = String(options.sessionLabel || '龙虾圆桌·选Skill');
|
|
2568
|
-
const hasRuntime = !!(core?.channel?.reply?.finalizeInboundContext &&
|
|
2569
|
-
core?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher);
|
|
2570
|
-
|
|
2571
|
-
if (hasRuntime) {
|
|
2572
|
-
try {
|
|
2573
|
-
return await callAIViaRuntime(
|
|
2574
|
-
api,
|
|
2575
|
-
core,
|
|
2576
|
-
prompt,
|
|
2577
|
-
sessionLabel,
|
|
2578
|
-
'进化室',
|
|
2579
|
-
senderId,
|
|
2580
|
-
{
|
|
2581
|
-
turnContext,
|
|
2582
|
-
roomMode: String(options.roomMode || ''),
|
|
2583
|
-
roomId: String(options.roomId || ''),
|
|
2584
|
-
}
|
|
2585
|
-
);
|
|
2586
|
-
} catch (err) {
|
|
2587
|
-
api.logger.warn(`[roundtable] callAI runtime 失败,降级 HTTP: ${err.message}`);
|
|
2588
|
-
const diag = (api && typeof api.__rtReportDiag === 'function') ? api.__rtReportDiag : null;
|
|
2589
|
-
diag?.("runtime_to_http_fallback", {
|
|
2590
|
-
level: "warn",
|
|
2591
|
-
message: `call_ai runtime failed: ${err.message}`,
|
|
2592
|
-
reason: String(err.message || err),
|
|
2593
|
-
detail: {
|
|
2594
|
-
phase: "call_ai",
|
|
2595
|
-
promptLen: String(prompt || '').length,
|
|
2596
|
-
turnContext,
|
|
2597
|
-
roomMode: String(options.roomMode || ''),
|
|
2598
|
-
roomId: String(options.roomId || ''),
|
|
2599
|
-
},
|
|
2600
|
-
}, 1200);
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2319
|
+
async function callAI(api, core, myName, prompt) {
|
|
2604
2320
|
return await callAIViaHTTP(prompt, 500, 30000, resolveGatewayHttpOptions(api));
|
|
2605
2321
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"lobster-roundtable"
|
|
6
6
|
],
|
|
7
7
|
"description": "Connect OpenClaw to the Lobster Roundtable service.",
|
|
8
|
-
"version": "3.0.
|
|
8
|
+
"version": "3.0.9",
|
|
9
9
|
"configSchema": {
|
|
10
10
|
"type": "object",
|
|
11
11
|
"additionalProperties": false,
|
|
@@ -67,4 +67,4 @@
|
|
|
67
67
|
"placeholder": "龙虾"
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
}
|
|
70
|
+
}
|
package/package.json
CHANGED