lobster-roundtable 3.0.2 → 3.0.3

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/index.js CHANGED
@@ -8,8 +8,8 @@
8
8
  */
9
9
 
10
10
  const fs = require("fs");
11
- const os = require("os");
12
11
  const path = require("path");
12
+ const { getOpenClawDir } = require("./src/env.js");
13
13
 
14
14
  const PLUGIN_ID = "lobster-roundtable";
15
15
  const PLUGIN_NAME = "龙虾圆桌";
@@ -19,7 +19,7 @@ const PLUGIN_NAME = "龙虾圆桌";
19
19
  */
20
20
  function cleanLegacyInstanceIdFromConfig(logger) {
21
21
  try {
22
- const ocDir = process.env.OPENCLAW_DIR || path.join(os.homedir(), ".openclaw");
22
+ const ocDir = getOpenClawDir();
23
23
  const configPath = path.join(ocDir, "openclaw.json");
24
24
  if (!fs.existsSync(configPath)) return false;
25
25
  const raw = fs.readFileSync(configPath, "utf-8");
package/main.js CHANGED
@@ -16,7 +16,13 @@ const httpModule = require("http");
16
16
  const httpsModule = require("https");
17
17
  const fsModule = require("fs");
18
18
  const cryptoModule = require("crypto");
19
- // Node.js 原生 HTTP 请求工具(替代 curl,避免 child_process 安全警告)
19
+ const {
20
+ getOpenClawDir,
21
+ isOpenClawConfigSyncEnabled,
22
+ getOpenClawApiPort,
23
+ getOpenClawApiToken,
24
+ } = require("./src/env.js");
25
+ // Node.js 原生 HTTP 请求工具(避免依赖外部命令)
20
26
  function httpRequest(urlStr, options = {}) {
21
27
  return new Promise((resolve, reject) => {
22
28
  const url = new URL(urlStr);
@@ -46,7 +52,7 @@ try {
46
52
  WebSocket = require("ws");
47
53
  } catch {
48
54
  try {
49
- const ocDir = process.env.OPENCLAW_DIR || os.homedir() + "/.openclaw";
55
+ const ocDir = getOpenClawDir();
50
56
  WebSocket = require(pathModule.join(ocDir, "node_modules", "ws"));
51
57
  } catch {
52
58
  // 最后的后备
@@ -54,8 +60,8 @@ try {
54
60
  }
55
61
 
56
62
  const CHANNEL_ID = "lobster-roundtable";
57
- const PLUGIN_VERSION = "3.0.2";
58
- const ENABLE_OPENCLAW_CONFIG_SYNC = String(process.env.LOBSTER_RT_SYNC_OPENCLAW || "").trim() === "1";
63
+ const PLUGIN_VERSION = "3.0.3";
64
+ const ENABLE_OPENCLAW_CONFIG_SYNC = isOpenClawConfigSyncEnabled();
59
65
  const OPENCLAW_CONFIG_ALLOWED_KEYS = new Set(["url", "token", "ownerToken", "name", "persona", "maxTokens"]);
60
66
 
61
67
  function normalizeIdentityId(raw, max = 96) {
@@ -65,7 +71,7 @@ function normalizeIdentityId(raw, max = 96) {
65
71
  }
66
72
 
67
73
  function buildOpenClawHome() {
68
- return process.env.OPENCLAW_DIR || pathModule.join(os.homedir(), ".openclaw");
74
+ return getOpenClawDir();
69
75
  }
70
76
 
71
77
  function sanitizeSkillDirName(name) {
@@ -2031,8 +2037,8 @@ function parseContext(context) {
2031
2037
  */
2032
2038
  function callAIViaHTTP(prompt, maxTokens = 500, timeoutMs = 45000) {
2033
2039
  return new Promise((resolve, reject) => {
2034
- const OC_PORT = parseInt(process.env.OPENCLAW_PORT || '18789', 10);
2035
- const OC_TOKEN = String(process.env.OPENCLAW_API_TOKEN || process.env.OPENCLAW_TOKEN || '').trim();
2040
+ const OC_PORT = getOpenClawApiPort();
2041
+ const OC_TOKEN = getOpenClawApiToken();
2036
2042
  const body = JSON.stringify({
2037
2043
  model: 'default',
2038
2044
  messages: [{ role: 'user', content: String(prompt || '') }],
@@ -5,7 +5,7 @@
5
5
  "lobster-roundtable"
6
6
  ],
7
7
  "description": "Connect OpenClaw to the Lobster Roundtable service.",
8
- "version": "3.0.2",
8
+ "version": "3.0.3",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lobster-roundtable",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "🦞 龙虾圆桌 OpenClaw 标准 Channel 插件 - 让你的 AI 自动参与多智能体圆桌讨论",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -41,4 +41,4 @@
41
41
  "optional": true
42
42
  }
43
43
  }
44
- }
44
+ }
package/src/channel.js CHANGED
@@ -146,79 +146,81 @@ const roundtablePlugin = {
146
146
  * - 必须保持 Promise 挂起,直�?abortSignal 触发�?resolve
147
147
  * - 参照 nextcloud-talk/src/channel.ts L337 的做�?
148
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 = await 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
- },
149
+ startAccount: async (ctx) => {
150
+ let bot = null;
151
+
152
+ try {
153
+ const initRoundtable = require("../main.js");
154
+ const apiAdapter = buildApiAdapter(ctx);
155
+ const core = ctx.runtime;
156
+
157
+ const hasRuntimeAPI = !!(
158
+ core?.channel?.reply?.finalizeInboundContext &&
159
+ core?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher
160
+ );
161
+
162
+ if (!hasRuntimeAPI) {
163
+ ctx.log?.info?.(
164
+ "[roundtable] runtime API 不可用,将使�?Gateway HTTP API 调用 AI(兼容模式)"
165
+ );
166
+ }
167
+
168
+ ctx.log?.info?.("[roundtable] Gateway 正在启动龙虾圆桌...");
169
+
170
+ bot = await initRoundtable(apiAdapter, core, hasRuntimeAPI);
171
+
172
+ if (!bot || typeof bot.stop !== "function") {
173
+ throw new Error("[roundtable] initRoundtable 未返回有效的 { stop } 对象");
174
+ }
175
+
176
+ ctx.setStatus?.({
177
+ accountId: ctx.accountId || "default",
178
+ running: true,
179
+ lastStartAt: Date.now(),
180
+ });
181
+ } catch (err) {
182
+ ctx.log?.error?.(`[roundtable] 启动失败: ${err.message}`);
183
+ ctx.setStatus?.({
184
+ accountId: ctx.accountId || "default",
185
+ running: false,
186
+ lastError: err.message,
187
+ });
188
+ throw err;
189
+ }
190
+
191
+ // 核心:Promise 保持 pending,直�?abortSignal 触发后 resolve
192
+ // 这样 Gateway 不会认为通道退出并触发重启循环
193
+ await new Promise((resolve) => {
194
+ const onAbort = () => {
195
+ ctx.log?.info?.("[roundtable] 收到 Gateway abortSignal,正在停�?..");
196
+ try {
197
+ bot?.stop?.();
198
+ } catch (err) {
199
+ ctx.log?.warn?.(`[roundtable] 停止 bot 时出现错误: ${err.message}`);
200
+ }
201
+ ctx.setStatus?.({
202
+ accountId: ctx.accountId || "default",
203
+ running: false,
204
+ lastStopAt: Date.now(),
205
+ });
206
+ resolve();
207
+ };
208
+
209
+ if (ctx.abortSignal?.aborted) {
210
+ onAbort();
211
+ return;
212
+ }
213
+
214
+ if (ctx.abortSignal) {
215
+ ctx.abortSignal.addEventListener("abort", onAbort, { once: true });
216
+ return;
217
+ }
218
+
219
+ // 如果没有 abortSignal,保持 pending:通道保持运行态
220
+ });
221
+
222
+ return { stop: () => { } };
223
+ },
222
224
  },
223
225
  };
224
226
 
package/src/env.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ const os = require("os");
4
+ const path = require("path");
5
+
6
+ function readEnv(name) {
7
+ if (!name) return "";
8
+ return String(process.env[name] || "");
9
+ }
10
+
11
+ function getOpenClawDir() {
12
+ return readEnv("OPENCLAW_DIR") || path.join(os.homedir(), ".openclaw");
13
+ }
14
+
15
+ function isOpenClawConfigSyncEnabled() {
16
+ return readEnv("LOBSTER_RT_SYNC_OPENCLAW").trim() === "1";
17
+ }
18
+
19
+ function getOpenClawApiPort() {
20
+ const parsed = parseInt(readEnv("OPENCLAW_PORT") || "18789", 10);
21
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 18789;
22
+ }
23
+
24
+ function getOpenClawApiToken() {
25
+ return (readEnv("OPENCLAW_API_TOKEN") || readEnv("OPENCLAW_TOKEN")).trim();
26
+ }
27
+
28
+ module.exports = {
29
+ getOpenClawDir,
30
+ isOpenClawConfigSyncEnabled,
31
+ getOpenClawApiPort,
32
+ getOpenClawApiToken,
33
+ };