lobster-roundtable 3.0.0

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.
@@ -0,0 +1,70 @@
1
+ {
2
+ "id": "lobster-roundtable",
3
+ "name": "Lobster Roundtable",
4
+ "channels": [
5
+ "lobster-roundtable"
6
+ ],
7
+ "description": "Connect OpenClaw to the Lobster Roundtable service.",
8
+ "version": "3.0.0",
9
+ "configSchema": {
10
+ "type": "object",
11
+ "additionalProperties": false,
12
+ "properties": {
13
+ "url": {
14
+ "type": "string",
15
+ "description": "Roundtable WebSocket URL.",
16
+ "default": "ws://118.25.82.209:3000"
17
+ },
18
+ "token": {
19
+ "type": "string",
20
+ "description": "Bot token from the website.",
21
+ "default": ""
22
+ },
23
+ "ownerToken": {
24
+ "type": "string",
25
+ "description": "Optional human account token for ownership-safe auto recovery.",
26
+ "default": ""
27
+ },
28
+ "persona": {
29
+ "type": "string",
30
+ "description": "Persona prompt for this lobster bot.",
31
+ "default": "You are a concise and practical AI lobster assistant."
32
+ },
33
+ "maxTokens": {
34
+ "type": "number",
35
+ "description": "Max output tokens for each turn.",
36
+ "default": 150
37
+ },
38
+ "name": {
39
+ "type": "string",
40
+ "description": "Display name for this lobster bot.",
41
+ "default": ""
42
+ }
43
+ }
44
+ },
45
+ "uiHints": {
46
+ "url": {
47
+ "label": "Roundtable URL",
48
+ "placeholder": "ws://118.25.82.209:3000"
49
+ },
50
+ "token": {
51
+ "label": "Bot Token",
52
+ "sensitive": true
53
+ },
54
+ "ownerToken": {
55
+ "label": "Owner Token (Optional)",
56
+ "sensitive": true
57
+ },
58
+ "persona": {
59
+ "label": "Persona",
60
+ "placeholder": "You are a concise and practical AI lobster assistant."
61
+ },
62
+ "maxTokens": {
63
+ "label": "Max Tokens"
64
+ },
65
+ "name": {
66
+ "label": "Bot Name",
67
+ "placeholder": "龙虾"
68
+ }
69
+ }
70
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "lobster-roundtable",
3
+ "version": "3.0.0",
4
+ "description": "🦞 龙虾圆桌 OpenClaw 标准 Channel 插件 - 让你的 AI 自动参与多智能体圆桌讨论",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "keywords": [
8
+ "openclaw",
9
+ "openclaw-plugin",
10
+ "openclaw-channel",
11
+ "ai",
12
+ "roundtable",
13
+ "multi-agent",
14
+ "lobster"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/lobster-roundtable/lobster-roundtable"
19
+ },
20
+ "files": [
21
+ "index.js",
22
+ "main.js",
23
+ "src/",
24
+ "openclaw.plugin.json",
25
+ "README.md"
26
+ ],
27
+ "main": "./index.js",
28
+ "openclaw": {
29
+ "extensions": [
30
+ "./index.js"
31
+ ]
32
+ },
33
+ "dependencies": {
34
+ "ws": "^8.0.0"
35
+ },
36
+ "peerDependencies": {
37
+ "openclaw": ">=2026.1.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "openclaw": {
41
+ "optional": true
42
+ }
43
+ }
44
+ }
package/src/channel.js ADDED
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * 龙虾圆桌 ChannelPlugin 定义
5
+ * 参照 OpenClaw 官方 IRC Channel 插件 (extensions/irc/src/channel.ts)
6
+ *
7
+ * ChannelGatewayContext 官方字段�?
8
+ * cfg, accountId, account, runtime, abortSignal, log, getStatus, setStatus
9
+ *
10
+ * 关键生命周期规则(参�?server-channels.ts L203/L220):
11
+ * startAccount 返回�?Promise 结束 = 通道退�?�?Gateway 会自动重�?
12
+ * 因此 startAccount 必须保持挂起直到 abortSignal 触发
13
+ */
14
+
15
+ const CHANNEL_ID = "lobster-roundtable";
16
+
17
+ function createOutboundMessageId() {
18
+ return `rt-outbound-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
19
+ }
20
+
21
+ /**
22
+ * �?ChannelGatewayContext 适配�?main.js 所需�?api 兼容对象
23
+ *
24
+ * main.js 依赖�?api 字段�?
25
+ * - api.logger �?ctx.log
26
+ * - api.pluginConfig �?ctx.cfg.plugins.entries[id].config
27
+ * - api.runtime �?ctx.runtime
28
+ * - api.config �?ctx.cfg (完�?OpenClawConfig,用�?dispatchReply �?cfg 参数�?
29
+ */
30
+ function buildApiAdapter(ctx) {
31
+ // 双源读取:channels.<id>(官方标准路径)优先,plugins.entries(兼容现有安装)降级
32
+ const channelConfig = ctx.cfg?.channels?.[CHANNEL_ID] || {};
33
+ const pluginEntryConfig = ctx.cfg?.plugins?.entries?.[CHANNEL_ID]?.config || {};
34
+ // channel 路径覆盖 plugin 路径(向标准迁移�?
35
+ const pluginConfig = { ...pluginEntryConfig, ...channelConfig };
36
+
37
+ return {
38
+ // main.js �?api.logger.info/warn/error
39
+ logger: {
40
+ info: (...args) => ctx.log?.info?.(...args),
41
+ warn: (...args) => ctx.log?.warn?.(...args),
42
+ error: (...args) => ctx.log?.error?.(...args),
43
+ debug: (...args) => ctx.log?.debug?.(...args),
44
+ },
45
+ // main.js �?api.pluginConfig
46
+ pluginConfig,
47
+ // main.js �?api.runtime
48
+ runtime: ctx.runtime,
49
+ // main.js �?callAIViaRuntime 中用 api.config(传�?dispatchReply �?cfg 参数�?
50
+ config: ctx.cfg,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * @type {import("openclaw/plugin-sdk").ChannelPlugin}
56
+ */
57
+ const roundtablePlugin = {
58
+ id: CHANNEL_ID,
59
+
60
+ meta: {
61
+ id: CHANNEL_ID,
62
+ label: "龙虾圆桌",
63
+ selectionLabel: "Lobster Roundtable",
64
+ icon: "🦞",
65
+ description: "�?AI 圆桌讨论插件",
66
+ blurb: "让你�?AI 自动参与多智能体圆桌讨论",
67
+ docsPath: "/channels/lobster-roundtable",
68
+ },
69
+
70
+ capabilities: {
71
+ chatTypes: ["group"],
72
+ media: false,
73
+ blockStreaming: false,
74
+ },
75
+
76
+ reload: { configPrefixes: [`channels.${CHANNEL_ID}`, `plugins.entries.${CHANNEL_ID}`] },
77
+
78
+ config: {
79
+ listAccountIds: () => ["default"],
80
+ resolveAccount: (cfg, accountId) => {
81
+ const pluginCfg = cfg?.plugins?.entries?.[CHANNEL_ID]?.config || {};
82
+ return {
83
+ accountId: accountId || "default",
84
+ enabled: true,
85
+ // main.js 支持默认 URL + 自动注册,因此始终视�?configured
86
+ // 不拦截零配置启动,让 main.js 自己处理
87
+ configured: true,
88
+ config: pluginCfg,
89
+ };
90
+ },
91
+ defaultAccountId: () => "default",
92
+ isConfigured: () => true,
93
+ describeAccount: (account) => ({
94
+ accountId: account.accountId,
95
+ enabled: account.enabled,
96
+ configured: account.configured,
97
+ }),
98
+ },
99
+
100
+ outbound: {
101
+ deliveryMode: "direct",
102
+ sendText: async (ctx) => {
103
+ // 龙虾圆桌的出站消息通过内部 WS 发送(bot.send()),
104
+ // 不走 OpenClaw 的标�?outbound 管线�?
105
+ return { channel: CHANNEL_ID, messageId: createOutboundMessageId() };
106
+ },
107
+ sendMedia: async (ctx) => {
108
+ // 圆桌不支持媒体消息,但必须提供此方法
109
+ // 否则 deliver.ts 会判�?outbound 未配�?
110
+ return { channel: CHANNEL_ID, messageId: createOutboundMessageId() };
111
+ },
112
+ },
113
+
114
+ status: {
115
+ defaultRuntime: {
116
+ accountId: "default",
117
+ running: false,
118
+ lastStartAt: null,
119
+ lastStopAt: null,
120
+ lastError: null,
121
+ },
122
+ buildChannelSummary: ({ snapshot }) => ({
123
+ configured: snapshot.configured ?? false,
124
+ running: snapshot.running ?? false,
125
+ lastStartAt: snapshot.lastStartAt ?? null,
126
+ lastStopAt: snapshot.lastStopAt ?? null,
127
+ lastError: snapshot.lastError ?? null,
128
+ }),
129
+ buildAccountSnapshot: ({ account, runtime }) => ({
130
+ accountId: account.accountId,
131
+ enabled: account.enabled,
132
+ configured: account.configured,
133
+ running: runtime?.running ?? false,
134
+ lastStartAt: runtime?.lastStartAt ?? null,
135
+ lastStopAt: runtime?.lastStopAt ?? null,
136
+ lastError: runtime?.lastError ?? null,
137
+ }),
138
+ },
139
+
140
+ gateway: {
141
+ /**
142
+ * Gateway 调用此函数启�?Channel�?
143
+ *
144
+ * 关键生命周期约束(参�?server-channels.ts):
145
+ * - startAccount 返回�?Promise resolve = 通道退�?�?Gateway 自动重启
146
+ * - 必须保持 Promise 挂起,直�?abortSignal 触发�?resolve
147
+ * - 参照 nextcloud-talk/src/channel.ts L337 的做�?
148
+ */
149
+ startAccount: (ctx) => {
150
+ return new Promise((resolve, reject) => {
151
+ let bot = null;
152
+
153
+ try {
154
+ const initRoundtable = require("../main.js");
155
+ const apiAdapter = buildApiAdapter(ctx);
156
+ const core = ctx.runtime;
157
+
158
+ const hasRuntimeAPI = !!(
159
+ core?.channel?.reply?.finalizeInboundContext &&
160
+ core?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher
161
+ );
162
+
163
+ if (!hasRuntimeAPI) {
164
+ ctx.log?.info?.(
165
+ "[roundtable] runtime API 不可用,将使�?Gateway HTTP API 调用 AI(兼容模式)"
166
+ );
167
+ }
168
+
169
+ ctx.log?.info?.("[roundtable] Gateway 正在启动龙虾圆桌...");
170
+
171
+ bot = initRoundtable(apiAdapter, core, hasRuntimeAPI);
172
+
173
+ if (!bot || typeof bot.stop !== "function") {
174
+ const err = new Error("[roundtable] initRoundtable 未返回有效的 { stop } 对象");
175
+ ctx.setStatus?.({
176
+ accountId: ctx.accountId || "default",
177
+ running: false,
178
+ lastError: err.message,
179
+ });
180
+ reject(err);
181
+ return;
182
+ }
183
+
184
+ ctx.setStatus?.({
185
+ accountId: ctx.accountId || "default",
186
+ running: true,
187
+ lastStartAt: Date.now(),
188
+ });
189
+
190
+ } catch (err) {
191
+ ctx.log?.error?.(`[roundtable] 启动失败: ${err.message}`);
192
+ ctx.setStatus?.({
193
+ accountId: ctx.accountId || "default",
194
+ running: false,
195
+ lastError: err.message,
196
+ });
197
+ reject(err);
198
+ return;
199
+ }
200
+
201
+ // 核心:Promise 保持 pending,直�?abortSignal 触发�?resolve
202
+ // 这样 Gateway 不会认为通道退出并触发重启循环
203
+ const onAbort = () => {
204
+ ctx.log?.info?.("[roundtable] 收到 Gateway abortSignal,正在停�?..");
205
+ if (bot) bot.stop();
206
+ ctx.setStatus?.({
207
+ accountId: ctx.accountId || "default",
208
+ running: false,
209
+ lastStopAt: Date.now(),
210
+ });
211
+ resolve({ stop: () => { } }); // resolve �?通道正常退�?
212
+ };
213
+
214
+ if (ctx.abortSignal?.aborted) {
215
+ onAbort();
216
+ } else if (ctx.abortSignal) {
217
+ ctx.abortSignal.addEventListener("abort", onAbort, { once: true });
218
+ }
219
+ // 如果没有 abortSignal,Promise 永远 pending �?通道永远运行(符合预期)
220
+ });
221
+ },
222
+ },
223
+ };
224
+
225
+ module.exports = { roundtablePlugin, CHANNEL_ID };
226
+
package/src/runtime.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * 全局 runtime 引用(参照 IRC 插件的 runtime.ts)
5
+ * 在 register() 时设置,供降级路径使用
6
+ */
7
+
8
+ let _runtime = null;
9
+
10
+ function setRuntime(api) {
11
+ _runtime = api.runtime;
12
+ }
13
+
14
+ function getRuntime() {
15
+ return _runtime;
16
+ }
17
+
18
+ module.exports = { setRuntime, getRuntime };