lobster-roundtable 3.0.11 → 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 +1 -1
- 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
|
|
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
|
+
|