lobster-roundtable 3.0.10 → 3.0.12
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 +103 -6
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/channel.js +161 -121
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.12";
|
|
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
|
|
|
@@ -2295,8 +2295,74 @@ function callAIViaHTTP(prompt, maxTokens = 500, timeoutMs = 45000, httpOptions =
|
|
|
2295
2295
|
}
|
|
2296
2296
|
|
|
2297
2297
|
/**
|
|
2298
|
-
*
|
|
2299
|
-
*
|
|
2298
|
+
* 通过 Gateway 内部 runtime API 调用 AI(标准 channel 插件路径)
|
|
2299
|
+
* 不依赖 HTTP 端点,直接走 agent 管线,适配所有 AI 后端
|
|
2300
|
+
*/
|
|
2301
|
+
async function callAIViaRuntime(api, core, prompt, timeoutMs = 45000) {
|
|
2302
|
+
const accountId = String(api?.accountId || "default");
|
|
2303
|
+
const sessionKey = `${CHANNEL_ID}:${accountId}:group:roundtable`;
|
|
2304
|
+
const timestamp = Date.now();
|
|
2305
|
+
|
|
2306
|
+
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
2307
|
+
Body: prompt,
|
|
2308
|
+
BodyForAgent: prompt,
|
|
2309
|
+
RawBody: prompt,
|
|
2310
|
+
CommandBody: prompt,
|
|
2311
|
+
From: `${CHANNEL_ID}:server`,
|
|
2312
|
+
To: `${CHANNEL_ID}:group:roundtable`,
|
|
2313
|
+
SessionKey: sessionKey,
|
|
2314
|
+
AccountId: accountId,
|
|
2315
|
+
ChatType: "group",
|
|
2316
|
+
ConversationLabel: "龙虾圆桌",
|
|
2317
|
+
SenderName: "圆桌服务器",
|
|
2318
|
+
SenderId: "roundtable-server",
|
|
2319
|
+
GroupSubject: "roundtable",
|
|
2320
|
+
Provider: CHANNEL_ID,
|
|
2321
|
+
Surface: CHANNEL_ID,
|
|
2322
|
+
MessageSid: `rt-${timestamp}-${Math.random().toString(36).slice(2, 8)}`,
|
|
2323
|
+
Timestamp: timestamp,
|
|
2324
|
+
OriginatingChannel: CHANNEL_ID,
|
|
2325
|
+
OriginatingTo: `${CHANNEL_ID}:group:roundtable`,
|
|
2326
|
+
CommandAuthorized: true,
|
|
2327
|
+
});
|
|
2328
|
+
|
|
2329
|
+
const parts = [];
|
|
2330
|
+
let dispatchError = null;
|
|
2331
|
+
|
|
2332
|
+
const dispatchResult = await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
2333
|
+
ctx: ctxPayload,
|
|
2334
|
+
cfg: api.config,
|
|
2335
|
+
dispatcherOptions: {
|
|
2336
|
+
deliver: async (payload, info) => {
|
|
2337
|
+
const text = typeof payload?.text === "string" ? payload.text.trim() : "";
|
|
2338
|
+
if (text) parts.push(text);
|
|
2339
|
+
},
|
|
2340
|
+
onError: (err, info) => {
|
|
2341
|
+
dispatchError = err;
|
|
2342
|
+
api.logger.error(`[roundtable] dispatch deliver error (${info?.kind || 'unknown'}): ${String(err)}`);
|
|
2343
|
+
},
|
|
2344
|
+
},
|
|
2345
|
+
});
|
|
2346
|
+
|
|
2347
|
+
const reply = parts.join("\n").trim();
|
|
2348
|
+
if (reply) return reply;
|
|
2349
|
+
|
|
2350
|
+
// 详细诊断信息
|
|
2351
|
+
const diagInfo = {
|
|
2352
|
+
queuedFinal: dispatchResult?.queuedFinal,
|
|
2353
|
+
counts: dispatchResult?.counts,
|
|
2354
|
+
partsLen: parts.length,
|
|
2355
|
+
dispatchError: dispatchError ? String(dispatchError) : null,
|
|
2356
|
+
sessionKey,
|
|
2357
|
+
accountId,
|
|
2358
|
+
};
|
|
2359
|
+
throw new Error(`runtime_no_reply: ${JSON.stringify(diagInfo)}`);
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
/**
|
|
2363
|
+
* 核心 AI 调用:runtime dispatch → HTTP 降级
|
|
2364
|
+
* runtime 是 OpenClaw 标准 channel 插件 API,适配所有 AI 后端(kimi/gemini/qwen/...)
|
|
2365
|
+
* HTTP 仅在 runtime 不可用时降级使用
|
|
2300
2366
|
*/
|
|
2301
2367
|
async function generateReply(api, core, myName, persona, maxTokens, msg, factualContext = '') {
|
|
2302
2368
|
const rawHistory = msg.history || parseContext(msg.context);
|
|
@@ -2310,14 +2376,45 @@ async function generateReply(api, core, myName, persona, maxTokens, msg, factual
|
|
|
2310
2376
|
: '';
|
|
2311
2377
|
|
|
2312
2378
|
const fullPrompt = `${systemPrompt}${memoryBlock}\n\n---\n\n${userMessage}`;
|
|
2313
|
-
|
|
2379
|
+
|
|
2380
|
+
// 1. 优先 runtime dispatch(标准路径,所有 OpenClaw 都支持)
|
|
2381
|
+
const hasRuntime = !!(core?.channel?.reply?.finalizeInboundContext &&
|
|
2382
|
+
core?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher);
|
|
2383
|
+
|
|
2384
|
+
if (hasRuntime) {
|
|
2385
|
+
try {
|
|
2386
|
+
return await callAIViaRuntime(api, core, fullPrompt, 65000);
|
|
2387
|
+
} catch (err) {
|
|
2388
|
+
api.logger.warn(`[roundtable] generateReply runtime failed: ${err.message}`);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
// 2. 降级 HTTP
|
|
2393
|
+
try {
|
|
2394
|
+
return await callAIViaHTTP(fullPrompt, maxTokens || 500, 65000, resolveGatewayHttpOptions(api));
|
|
2395
|
+
} catch (httpErr) {
|
|
2396
|
+
api.logger.warn(`[roundtable] generateReply HTTP also failed: ${httpErr.message}`);
|
|
2397
|
+
throw httpErr;
|
|
2398
|
+
}
|
|
2314
2399
|
}
|
|
2315
2400
|
|
|
2316
2401
|
/**
|
|
2317
2402
|
* 精简版 AI 调用:给一个 prompt,拿一个回复
|
|
2318
|
-
* 用于进化室选 Skill
|
|
2319
|
-
*
|
|
2403
|
+
* 用于进化室选 Skill、评审、投票等场景
|
|
2404
|
+
* runtime dispatch → HTTP 降级
|
|
2320
2405
|
*/
|
|
2321
2406
|
async function callAI(api, core, myName, prompt) {
|
|
2407
|
+
const hasRuntime = !!(core?.channel?.reply?.finalizeInboundContext &&
|
|
2408
|
+
core?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher);
|
|
2409
|
+
|
|
2410
|
+
if (hasRuntime) {
|
|
2411
|
+
try {
|
|
2412
|
+
return await callAIViaRuntime(api, core, prompt, 30000);
|
|
2413
|
+
} catch (err) {
|
|
2414
|
+
api.logger.warn(`[roundtable] callAI runtime failed: ${err.message}`);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2322
2418
|
return await callAIViaHTTP(prompt, 500, 30000, resolveGatewayHttpOptions(api));
|
|
2323
2419
|
}
|
|
2420
|
+
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/channel.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 龙虾圆桌 ChannelPlugin 定义
|
|
5
|
-
* 参照 OpenClaw 官方 IRC Channel 插件 (extensions/irc/src/channel.ts)
|
|
6
|
-
*
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 龙虾圆桌 ChannelPlugin 定义
|
|
5
|
+
* 参照 OpenClaw 官方 IRC Channel 插件 (extensions/irc/src/channel.ts)
|
|
6
|
+
*
|
|
7
7
|
* ChannelGatewayContext 官方字段�?
|
|
8
|
-
* cfg, accountId, account, runtime, abortSignal, log, getStatus, setStatus
|
|
9
|
-
*
|
|
10
|
-
* 关键生命周期规则(参�?server-channels.ts L203/L220):
|
|
8
|
+
* cfg, accountId, account, runtime, abortSignal, log, getStatus, setStatus
|
|
9
|
+
*
|
|
10
|
+
* 关键生命周期规则(参�?server-channels.ts L203/L220):
|
|
11
11
|
* startAccount 返回�?Promise 结束 = 通道退�?�?Gateway 会自动重�?
|
|
12
|
-
* 因此 startAccount 必须保持挂起直到 abortSignal 触发
|
|
13
|
-
*/
|
|
14
|
-
|
|
12
|
+
* 因此 startAccount 必须保持挂起直到 abortSignal 触发
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
15
|
const CHANNEL_ID = "lobster-roundtable";
|
|
16
16
|
|
|
17
17
|
function createOutboundMessageId() {
|
|
@@ -32,31 +32,31 @@ function mergeConfigPreferExplicitChannel(pluginEntryConfig, channelConfig) {
|
|
|
32
32
|
}
|
|
33
33
|
return merged;
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* �?ChannelGatewayContext 适配�?main.js 所需�?api 兼容对象
|
|
38
|
-
*
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* �?ChannelGatewayContext 适配�?main.js 所需�?api 兼容对象
|
|
38
|
+
*
|
|
39
39
|
* main.js 依赖�?api 字段�?
|
|
40
|
-
* - api.logger �?ctx.log
|
|
41
|
-
* - api.pluginConfig �?ctx.cfg.plugins.entries[id].config
|
|
42
|
-
* - api.runtime �?ctx.runtime
|
|
40
|
+
* - api.logger �?ctx.log
|
|
41
|
+
* - api.pluginConfig �?ctx.cfg.plugins.entries[id].config
|
|
42
|
+
* - api.runtime �?ctx.runtime
|
|
43
43
|
* - api.config �?ctx.cfg (完�?OpenClawConfig,用�?dispatchReply �?cfg 参数�?
|
|
44
|
-
*/
|
|
44
|
+
*/
|
|
45
45
|
function buildApiAdapter(ctx) {
|
|
46
46
|
// 双源读取:channels.<id>(官方标准路径)优先,plugins.entries(兼容现有安装)降级。
|
|
47
47
|
// 关键:channels 中空字符串不覆盖 plugins.entries 中已有 token/url,避免“loaded 但离线”。
|
|
48
48
|
const channelConfig = ctx.cfg?.channels?.[CHANNEL_ID] || {};
|
|
49
49
|
const pluginEntryConfig = ctx.cfg?.plugins?.entries?.[CHANNEL_ID]?.config || {};
|
|
50
50
|
const pluginConfig = mergeConfigPreferExplicitChannel(pluginEntryConfig, channelConfig);
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
// main.js �?api.logger.info/warn/error
|
|
54
|
-
logger: {
|
|
55
|
-
info: (...args) => ctx.log?.info?.(...args),
|
|
56
|
-
warn: (...args) => ctx.log?.warn?.(...args),
|
|
57
|
-
error: (...args) => ctx.log?.error?.(...args),
|
|
58
|
-
debug: (...args) => ctx.log?.debug?.(...args),
|
|
59
|
-
},
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
// main.js �?api.logger.info/warn/error
|
|
54
|
+
logger: {
|
|
55
|
+
info: (...args) => ctx.log?.info?.(...args),
|
|
56
|
+
warn: (...args) => ctx.log?.warn?.(...args),
|
|
57
|
+
error: (...args) => ctx.log?.error?.(...args),
|
|
58
|
+
debug: (...args) => ctx.log?.debug?.(...args),
|
|
59
|
+
},
|
|
60
60
|
// main.js �?api.pluginConfig
|
|
61
61
|
pluginConfig,
|
|
62
62
|
// main.js �?api.runtime
|
|
@@ -67,32 +67,32 @@ function buildApiAdapter(ctx) {
|
|
|
67
67
|
config: ctx.cfg,
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* @type {import("openclaw/plugin-sdk").ChannelPlugin}
|
|
73
|
-
*/
|
|
74
|
-
const roundtablePlugin = {
|
|
75
|
-
id: CHANNEL_ID,
|
|
76
|
-
|
|
77
|
-
meta: {
|
|
78
|
-
id: CHANNEL_ID,
|
|
79
|
-
label: "龙虾圆桌",
|
|
80
|
-
selectionLabel: "Lobster Roundtable",
|
|
81
|
-
icon: "🦞",
|
|
82
|
-
description: "�?AI 圆桌讨论插件",
|
|
83
|
-
blurb: "让你�?AI 自动参与多智能体圆桌讨论",
|
|
84
|
-
docsPath: "/channels/lobster-roundtable",
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
capabilities: {
|
|
88
|
-
chatTypes: ["group"],
|
|
89
|
-
media: false,
|
|
90
|
-
blockStreaming: false,
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
reload: { configPrefixes: [`channels.${CHANNEL_ID}`, `plugins.entries.${CHANNEL_ID}`] },
|
|
94
|
-
|
|
95
|
-
config: {
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @type {import("openclaw/plugin-sdk").ChannelPlugin}
|
|
73
|
+
*/
|
|
74
|
+
const roundtablePlugin = {
|
|
75
|
+
id: CHANNEL_ID,
|
|
76
|
+
|
|
77
|
+
meta: {
|
|
78
|
+
id: CHANNEL_ID,
|
|
79
|
+
label: "龙虾圆桌",
|
|
80
|
+
selectionLabel: "Lobster Roundtable",
|
|
81
|
+
icon: "🦞",
|
|
82
|
+
description: "�?AI 圆桌讨论插件",
|
|
83
|
+
blurb: "让你�?AI 自动参与多智能体圆桌讨论",
|
|
84
|
+
docsPath: "/channels/lobster-roundtable",
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
capabilities: {
|
|
88
|
+
chatTypes: ["group"],
|
|
89
|
+
media: false,
|
|
90
|
+
blockStreaming: false,
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
reload: { configPrefixes: [`channels.${CHANNEL_ID}`, `plugins.entries.${CHANNEL_ID}`] },
|
|
94
|
+
|
|
95
|
+
config: {
|
|
96
96
|
listAccountIds: () => ["default"],
|
|
97
97
|
resolveAccount: (cfg, accountId) => {
|
|
98
98
|
const channelConfig = cfg?.channels?.[CHANNEL_ID] || {};
|
|
@@ -101,70 +101,70 @@ const roundtablePlugin = {
|
|
|
101
101
|
return {
|
|
102
102
|
accountId: accountId || "default",
|
|
103
103
|
enabled: true,
|
|
104
|
-
// main.js 支持默认 URL + 自动注册,因此始终视�?configured
|
|
105
|
-
// 不拦截零配置启动,让 main.js 自己处理
|
|
106
|
-
configured: true,
|
|
107
|
-
config: pluginCfg,
|
|
108
|
-
};
|
|
109
|
-
},
|
|
110
|
-
defaultAccountId: () => "default",
|
|
111
|
-
isConfigured: () => true,
|
|
112
|
-
describeAccount: (account) => ({
|
|
113
|
-
accountId: account.accountId,
|
|
114
|
-
enabled: account.enabled,
|
|
115
|
-
configured: account.configured,
|
|
116
|
-
}),
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
outbound: {
|
|
120
|
-
deliveryMode: "direct",
|
|
121
|
-
sendText: async (ctx) => {
|
|
122
|
-
// 龙虾圆桌的出站消息通过内部 WS 发送(bot.send()),
|
|
104
|
+
// main.js 支持默认 URL + 自动注册,因此始终视�?configured
|
|
105
|
+
// 不拦截零配置启动,让 main.js 自己处理
|
|
106
|
+
configured: true,
|
|
107
|
+
config: pluginCfg,
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
defaultAccountId: () => "default",
|
|
111
|
+
isConfigured: () => true,
|
|
112
|
+
describeAccount: (account) => ({
|
|
113
|
+
accountId: account.accountId,
|
|
114
|
+
enabled: account.enabled,
|
|
115
|
+
configured: account.configured,
|
|
116
|
+
}),
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
outbound: {
|
|
120
|
+
deliveryMode: "direct",
|
|
121
|
+
sendText: async (ctx) => {
|
|
122
|
+
// 龙虾圆桌的出站消息通过内部 WS 发送(bot.send()),
|
|
123
123
|
// 不走 OpenClaw 的标�?outbound 管线�?
|
|
124
|
-
return { channel: CHANNEL_ID, messageId: createOutboundMessageId() };
|
|
125
|
-
},
|
|
126
|
-
sendMedia: async (ctx) => {
|
|
127
|
-
// 圆桌不支持媒体消息,但必须提供此方法
|
|
124
|
+
return { channel: CHANNEL_ID, messageId: createOutboundMessageId() };
|
|
125
|
+
},
|
|
126
|
+
sendMedia: async (ctx) => {
|
|
127
|
+
// 圆桌不支持媒体消息,但必须提供此方法
|
|
128
128
|
// 否则 deliver.ts 会判�?outbound 未配�?
|
|
129
|
-
return { channel: CHANNEL_ID, messageId: createOutboundMessageId() };
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
status: {
|
|
134
|
-
defaultRuntime: {
|
|
135
|
-
accountId: "default",
|
|
136
|
-
running: false,
|
|
137
|
-
lastStartAt: null,
|
|
138
|
-
lastStopAt: null,
|
|
139
|
-
lastError: null,
|
|
140
|
-
},
|
|
141
|
-
buildChannelSummary: ({ snapshot }) => ({
|
|
142
|
-
configured: snapshot.configured ?? false,
|
|
143
|
-
running: snapshot.running ?? false,
|
|
144
|
-
lastStartAt: snapshot.lastStartAt ?? null,
|
|
145
|
-
lastStopAt: snapshot.lastStopAt ?? null,
|
|
146
|
-
lastError: snapshot.lastError ?? null,
|
|
147
|
-
}),
|
|
148
|
-
buildAccountSnapshot: ({ account, runtime }) => ({
|
|
149
|
-
accountId: account.accountId,
|
|
150
|
-
enabled: account.enabled,
|
|
151
|
-
configured: account.configured,
|
|
152
|
-
running: runtime?.running ?? false,
|
|
153
|
-
lastStartAt: runtime?.lastStartAt ?? null,
|
|
154
|
-
lastStopAt: runtime?.lastStopAt ?? null,
|
|
155
|
-
lastError: runtime?.lastError ?? null,
|
|
156
|
-
}),
|
|
157
|
-
},
|
|
158
|
-
|
|
159
|
-
gateway: {
|
|
160
|
-
/**
|
|
129
|
+
return { channel: CHANNEL_ID, messageId: createOutboundMessageId() };
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
status: {
|
|
134
|
+
defaultRuntime: {
|
|
135
|
+
accountId: "default",
|
|
136
|
+
running: false,
|
|
137
|
+
lastStartAt: null,
|
|
138
|
+
lastStopAt: null,
|
|
139
|
+
lastError: null,
|
|
140
|
+
},
|
|
141
|
+
buildChannelSummary: ({ snapshot }) => ({
|
|
142
|
+
configured: snapshot.configured ?? false,
|
|
143
|
+
running: snapshot.running ?? false,
|
|
144
|
+
lastStartAt: snapshot.lastStartAt ?? null,
|
|
145
|
+
lastStopAt: snapshot.lastStopAt ?? null,
|
|
146
|
+
lastError: snapshot.lastError ?? null,
|
|
147
|
+
}),
|
|
148
|
+
buildAccountSnapshot: ({ account, runtime }) => ({
|
|
149
|
+
accountId: account.accountId,
|
|
150
|
+
enabled: account.enabled,
|
|
151
|
+
configured: account.configured,
|
|
152
|
+
running: runtime?.running ?? false,
|
|
153
|
+
lastStartAt: runtime?.lastStartAt ?? null,
|
|
154
|
+
lastStopAt: runtime?.lastStopAt ?? null,
|
|
155
|
+
lastError: runtime?.lastError ?? null,
|
|
156
|
+
}),
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
gateway: {
|
|
160
|
+
/**
|
|
161
161
|
* Gateway 调用此函数启�?Channel�?
|
|
162
|
-
*
|
|
163
|
-
* 关键生命周期约束(参�?server-channels.ts):
|
|
164
|
-
* - startAccount 返回�?Promise resolve = 通道退�?�?Gateway 自动重启
|
|
165
|
-
* - 必须保持 Promise 挂起,直�?abortSignal 触发�?resolve
|
|
162
|
+
*
|
|
163
|
+
* 关键生命周期约束(参�?server-channels.ts):
|
|
164
|
+
* - startAccount 返回�?Promise resolve = 通道退�?�?Gateway 自动重启
|
|
165
|
+
* - 必须保持 Promise 挂起,直�?abortSignal 触发�?resolve
|
|
166
166
|
* - 参照 nextcloud-talk/src/channel.ts L337 的做�?
|
|
167
|
-
*/
|
|
167
|
+
*/
|
|
168
168
|
startAccount: async (ctx) => {
|
|
169
169
|
let bot = null;
|
|
170
170
|
|
|
@@ -180,8 +180,48 @@ const roundtablePlugin = {
|
|
|
180
180
|
|
|
181
181
|
if (!hasRuntimeAPI) {
|
|
182
182
|
ctx.log?.info?.(
|
|
183
|
-
"[roundtable] runtime API
|
|
183
|
+
"[roundtable] runtime API 不可用,将使?Gateway HTTP API 调用 AI(兼容模式)"
|
|
184
184
|
);
|
|
185
|
+
|
|
186
|
+
// ═══ 自动启用 chatCompletions HTTP 端点 ═══
|
|
187
|
+
// OpenClaw 默认禁用 /v1/chat/completions(405),需要在配置中启用
|
|
188
|
+
try {
|
|
189
|
+
const os = require("os");
|
|
190
|
+
const path = require("path");
|
|
191
|
+
const fs = require("fs");
|
|
192
|
+
const openclawDir = process.env.OPENCLAW_DIR || path.join(os.homedir(), ".openclaw");
|
|
193
|
+
const configPath = path.join(openclawDir, "openclaw.json");
|
|
194
|
+
|
|
195
|
+
if (fs.existsSync(configPath)) {
|
|
196
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
197
|
+
const config = JSON.parse(raw);
|
|
198
|
+
const enabled = config?.gateway?.http?.endpoints?.chatCompletions?.enabled;
|
|
199
|
+
|
|
200
|
+
if (!enabled) {
|
|
201
|
+
// 安全地合并配置,不覆盖现有字段
|
|
202
|
+
config.gateway = config.gateway || {};
|
|
203
|
+
config.gateway.http = config.gateway.http || {};
|
|
204
|
+
config.gateway.http.endpoints = config.gateway.http.endpoints || {};
|
|
205
|
+
config.gateway.http.endpoints.chatCompletions = { enabled: true };
|
|
206
|
+
|
|
207
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
208
|
+
ctx.log?.info?.(
|
|
209
|
+
"[roundtable] ✅ 已自动启用 gateway.http.endpoints.chatCompletions"
|
|
210
|
+
);
|
|
211
|
+
ctx.log?.info?.(
|
|
212
|
+
"[roundtable] ⚠️ 需要重启 Gateway 使配置生效: openclaw gateway restart"
|
|
213
|
+
);
|
|
214
|
+
} else {
|
|
215
|
+
ctx.log?.info?.(
|
|
216
|
+
"[roundtable] chatCompletions 端点已启用,HTTP AI 调用可用"
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch (autoEnableErr) {
|
|
221
|
+
ctx.log?.warn?.(
|
|
222
|
+
`[roundtable] 自动启用 chatCompletions 失败: ${autoEnableErr.message}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
185
225
|
}
|
|
186
226
|
|
|
187
227
|
ctx.log?.info?.("[roundtable] Gateway 正在启动龙虾圆桌...");
|
|
@@ -240,8 +280,8 @@ const roundtablePlugin = {
|
|
|
240
280
|
|
|
241
281
|
return { stop: () => { } };
|
|
242
282
|
},
|
|
243
|
-
},
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
module.exports = { roundtablePlugin, CHANNEL_ID };
|
|
247
|
-
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
module.exports = { roundtablePlugin, CHANNEL_ID };
|
|
287
|
+
|