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 +2 -2
- package/main.js +13 -7
- package/openclaw.plugin.json +2 -2
- package/package.json +2 -2
- package/src/channel.js +75 -73
- package/src/env.js +33 -0
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 =
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
58
|
-
const ENABLE_OPENCLAW_CONFIG_SYNC =
|
|
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
|
|
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 =
|
|
2035
|
-
const OC_TOKEN =
|
|
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 || '') }],
|
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.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
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
ctx.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (ctx.abortSignal
|
|
215
|
-
onAbort
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// 如果没有 abortSignal
|
|
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
|
+
};
|