claw-subagent-service 0.0.25 → 0.0.27
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/package.json +1 -1
- package/service/rongcloud/openclaw-client.js +148 -29
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ const net = require('net');
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const axios = require('axios');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 获取实际用户主目录(SYSTEM 账户下 os.homedir() 返回 systemprofile)
|
|
@@ -32,6 +33,66 @@ function getRealHomeDir() {
|
|
|
32
33
|
return homeDir;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* 构建 OpenClaw 需要的完整环境变量
|
|
38
|
+
* Windows SYSTEM 账户下必须设置 HOMEDRIVE/HOMEPATH/APPDATA 等
|
|
39
|
+
* 策略:从现有环境变量中推断正确路径,避免硬编码任何用户名或目录结构
|
|
40
|
+
*/
|
|
41
|
+
function getOpenClawEnv(baseEnv = process.env) {
|
|
42
|
+
const realHome = getRealHomeDir();
|
|
43
|
+
const env = { ...baseEnv };
|
|
44
|
+
const systemHome = os.homedir();
|
|
45
|
+
|
|
46
|
+
// 基础 home 目录变量
|
|
47
|
+
env.USERPROFILE = realHome;
|
|
48
|
+
env.HOME = realHome;
|
|
49
|
+
|
|
50
|
+
if (process.platform === 'win32') {
|
|
51
|
+
// 从 realHome 推断 HOMEDRIVE / HOMEPATH(匹配盘符 + 路径)
|
|
52
|
+
const match = realHome.match(/^([A-Za-z]:)(.*)$/);
|
|
53
|
+
if (match) {
|
|
54
|
+
env.HOMEDRIVE = match[1];
|
|
55
|
+
env.HOMEPATH = match[2];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 修复路径:把现有环境变量中的 systemprofile home 路径替换为真实用户 home 路径
|
|
60
|
+
* 这样能处理 Roaming Profiles、AppData 重定向、非标准安装等各种情况
|
|
61
|
+
*/
|
|
62
|
+
const fixPath = (originalPath) => {
|
|
63
|
+
if (!originalPath) return null;
|
|
64
|
+
const lowerOriginal = originalPath.toLowerCase();
|
|
65
|
+
const lowerSystemHome = systemHome.toLowerCase();
|
|
66
|
+
if (lowerOriginal.includes(lowerSystemHome)) {
|
|
67
|
+
const idx = lowerOriginal.indexOf(lowerSystemHome);
|
|
68
|
+
return originalPath.substring(0, idx) + realHome + originalPath.substring(idx + systemHome.length);
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// APPDATA:优先从现有变量推断,保留原始目录结构
|
|
74
|
+
if (baseEnv.APPDATA) {
|
|
75
|
+
const fixed = fixPath(baseEnv.APPDATA);
|
|
76
|
+
if (fixed) env.APPDATA = fixed;
|
|
77
|
+
}
|
|
78
|
+
// 兜底:按标准结构拼接(仅在无法推断时使用)
|
|
79
|
+
if (!env.APPDATA) {
|
|
80
|
+
env.APPDATA = path.join(realHome, 'AppData', 'Roaming');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// LOCALAPPDATA:同上
|
|
84
|
+
if (baseEnv.LOCALAPPDATA) {
|
|
85
|
+
const fixed = fixPath(baseEnv.LOCALAPPDATA);
|
|
86
|
+
if (fixed) env.LOCALAPPDATA = fixed;
|
|
87
|
+
}
|
|
88
|
+
if (!env.LOCALAPPDATA) {
|
|
89
|
+
env.LOCALAPPDATA = path.join(realHome, 'AppData', 'Local');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return env;
|
|
94
|
+
}
|
|
95
|
+
|
|
35
96
|
/**
|
|
36
97
|
* 检测端口是否监听
|
|
37
98
|
*/
|
|
@@ -67,24 +128,21 @@ function startOpenClawGateway(log) {
|
|
|
67
128
|
windowsHide: true,
|
|
68
129
|
detached: true,
|
|
69
130
|
stdio: 'ignore',
|
|
70
|
-
env:
|
|
71
|
-
...process.env,
|
|
72
|
-
USERPROFILE: getRealHomeDir(),
|
|
73
|
-
HOME: getRealHomeDir(),
|
|
74
|
-
},
|
|
131
|
+
env: getOpenClawEnv(),
|
|
75
132
|
});
|
|
76
133
|
|
|
77
134
|
child.unref();
|
|
78
135
|
|
|
79
|
-
// 等待 gateway 启动(最多
|
|
136
|
+
// 等待 gateway 启动(最多 20 秒)
|
|
80
137
|
let attempts = 0;
|
|
81
|
-
const maxAttempts =
|
|
138
|
+
const maxAttempts = 20;
|
|
82
139
|
const interval = setInterval(async () => {
|
|
83
140
|
attempts++;
|
|
84
|
-
const
|
|
85
|
-
|
|
141
|
+
const gatewayRunning = await checkPort(18789);
|
|
142
|
+
const apiRunning = await checkPort(5678);
|
|
143
|
+
if (gatewayRunning) {
|
|
86
144
|
clearInterval(interval);
|
|
87
|
-
log?.info(
|
|
145
|
+
log?.info(`[OpenClawClient] OpenClaw gateway 启动成功 (18789),API 状态: ${apiRunning ? '就绪' : '未就绪'}`);
|
|
88
146
|
resolve(true);
|
|
89
147
|
} else if (attempts >= maxAttempts) {
|
|
90
148
|
clearInterval(interval);
|
|
@@ -114,18 +172,36 @@ class OpenClawClient {
|
|
|
114
172
|
async ensureGatewayRunning() {
|
|
115
173
|
if (this.gatewayStarted) return true;
|
|
116
174
|
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
175
|
+
const gatewayRunning = await checkPort(18789);
|
|
176
|
+
const apiRunning = await checkPort(5678);
|
|
177
|
+
|
|
178
|
+
if (gatewayRunning && apiRunning) {
|
|
179
|
+
this.log?.info('[OpenClawClient] OpenClaw gateway 和 API 均已就绪');
|
|
120
180
|
this.gatewayStarted = true;
|
|
121
181
|
return true;
|
|
122
182
|
}
|
|
123
183
|
|
|
184
|
+
if (gatewayRunning && !apiRunning) {
|
|
185
|
+
this.log?.info('[OpenClawClient] gateway 已运行 (18789),但 API (5678) 未就绪,继续等待...');
|
|
186
|
+
// 等待最多 10 秒让 API 就绪
|
|
187
|
+
for (let i = 0; i < 10; i++) {
|
|
188
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
189
|
+
if (await checkPort(5678)) {
|
|
190
|
+
this.log?.info('[OpenClawClient] API (5678) 已就绪');
|
|
191
|
+
this.gatewayStarted = true;
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
this.log?.warn('[OpenClawClient] API (5678) 等待超时,gateway 可能未完全初始化');
|
|
196
|
+
// gateway 在运行但 API 没好,仍然允许继续,让 HTTP fallback 到 CLI
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
124
200
|
// 避免并发启动
|
|
125
201
|
if (this.gatewayStarting) {
|
|
126
202
|
this.log?.info('[OpenClawClient] gateway 正在启动中,等待...');
|
|
127
|
-
// 等待最多
|
|
128
|
-
for (let i = 0; i <
|
|
203
|
+
// 等待最多 25 秒
|
|
204
|
+
for (let i = 0; i < 25; i++) {
|
|
129
205
|
await new Promise(r => setTimeout(r, 1000));
|
|
130
206
|
if (await checkPort(18789)) {
|
|
131
207
|
this.gatewayStarted = true;
|
|
@@ -159,6 +235,47 @@ class OpenClawClient {
|
|
|
159
235
|
|
|
160
236
|
this.log?.info(`[OpenClawClient] 准备发送消息到 OpenClaw,from=${fromUser}, message=${message.substring(0, 50)}`);
|
|
161
237
|
|
|
238
|
+
const gatewayUrl = 'http://127.0.0.1:5678';
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const response = await axios.post(
|
|
242
|
+
`${gatewayUrl}/v1/chat/completions`,
|
|
243
|
+
{
|
|
244
|
+
model: 'claw-local',
|
|
245
|
+
messages: [
|
|
246
|
+
{ role: 'user', content: message }
|
|
247
|
+
],
|
|
248
|
+
stream: false
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
headers: { 'Content-Type': 'application/json' },
|
|
252
|
+
timeout: 120000
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
if (response.status === 200 && response.data) {
|
|
257
|
+
const result = response.data;
|
|
258
|
+
const aiMessage = result.choices?.[0]?.message;
|
|
259
|
+
const content = aiMessage?.content;
|
|
260
|
+
if (content) {
|
|
261
|
+
this.log?.info(`[OpenClawClient] AI 回复: ${content.substring(0, 50)}...`);
|
|
262
|
+
return content;
|
|
263
|
+
}
|
|
264
|
+
this.log?.warn('[OpenClawClient] OpenClaw 返回了空响应');
|
|
265
|
+
return 'OpenClaw 未返回有效响应';
|
|
266
|
+
} else {
|
|
267
|
+
this.log?.error(`[OpenClawClient] OpenClaw 返回错误: ${response.status}`);
|
|
268
|
+
return `OpenClaw 返回错误: ${response.status}`;
|
|
269
|
+
}
|
|
270
|
+
} catch (err) {
|
|
271
|
+
this.log?.error(`[OpenClawClient] HTTP 调用失败: ${err.message}`);
|
|
272
|
+
// fallback 到 CLI 调用
|
|
273
|
+
this.log?.info('[OpenClawClient] HTTP 失败,尝试 CLI fallback');
|
|
274
|
+
return this.chatViaCLI(message, fromUser);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async chatViaCLI(message, fromUser) {
|
|
162
279
|
const sessionId = `clawmessenger-${fromUser}`;
|
|
163
280
|
const escapedMessage = message
|
|
164
281
|
.replace(/\\/g, '\\\\')
|
|
@@ -183,35 +300,37 @@ class OpenClawClient {
|
|
|
183
300
|
shell: true,
|
|
184
301
|
windowsHide: true,
|
|
185
302
|
env: {
|
|
186
|
-
...
|
|
303
|
+
...getOpenClawEnv(),
|
|
187
304
|
OPENCLAW_GATEWAY_URL: gatewayUrl,
|
|
188
|
-
USERPROFILE: realHome,
|
|
189
|
-
HOME: realHome,
|
|
190
305
|
},
|
|
191
306
|
});
|
|
192
307
|
|
|
193
|
-
|
|
308
|
+
this.log?.info(`[OpenClawClient] CLI 子进程 PID=${child.pid}`);
|
|
309
|
+
|
|
194
310
|
child.stdout.on('data', (chunk) => {
|
|
195
311
|
stdout += chunk.toString();
|
|
196
312
|
});
|
|
197
313
|
child.stderr.on('data', (chunk) => {
|
|
198
|
-
|
|
314
|
+
const text = chunk.toString();
|
|
315
|
+
stderr += text;
|
|
316
|
+
// 实时记录 stderr,方便调试卡死问题
|
|
317
|
+
this.log?.info(`[OpenClawClient] CLI stderr: ${text.trim().substring(0, 200)}`);
|
|
199
318
|
});
|
|
200
319
|
|
|
201
|
-
// 超时兜底(
|
|
320
|
+
// 超时兜底(2 分钟)
|
|
202
321
|
const timeout = setTimeout(() => {
|
|
203
322
|
killed = true;
|
|
204
323
|
child.kill('SIGTERM');
|
|
205
|
-
this.log?.error('[OpenClawClient]
|
|
206
|
-
},
|
|
324
|
+
this.log?.error('[OpenClawClient] CLI 执行超时(2分钟),强制终止');
|
|
325
|
+
}, 120000);
|
|
207
326
|
|
|
208
327
|
child.on('error', (err) => {
|
|
209
328
|
clearTimeout(timeout);
|
|
210
|
-
this.log?.error(`[OpenClawClient] 子进程错误: ${err.message}`);
|
|
329
|
+
this.log?.error(`[OpenClawClient] CLI 子进程错误: ${err.message}`);
|
|
211
330
|
if (err.code === 'ENOENT') {
|
|
212
331
|
resolve('找不到 openclaw 命令');
|
|
213
332
|
} else {
|
|
214
|
-
resolve(`OpenClaw 调用失败: ${err.message}`);
|
|
333
|
+
resolve(`OpenClaw CLI 调用失败: ${err.message}`);
|
|
215
334
|
}
|
|
216
335
|
});
|
|
217
336
|
|
|
@@ -219,17 +338,17 @@ class OpenClawClient {
|
|
|
219
338
|
clearTimeout(timeout);
|
|
220
339
|
|
|
221
340
|
if (killed) {
|
|
222
|
-
resolve('OpenClaw 响应超时');
|
|
341
|
+
resolve('OpenClaw CLI 响应超时');
|
|
223
342
|
return;
|
|
224
343
|
}
|
|
225
344
|
|
|
226
|
-
this.log?.info(`[OpenClawClient] 进程退出 code=${code}`);
|
|
345
|
+
this.log?.info(`[OpenClawClient] CLI 进程退出 code=${code}`);
|
|
227
346
|
this.log?.info(`[OpenClawClient] stdout 长度: ${stdout.length}, stderr 长度: ${stderr.length}`);
|
|
228
347
|
|
|
229
348
|
if (code !== 0) {
|
|
230
349
|
const errOutput = stderr || stdout || '';
|
|
231
|
-
this.log?.error(`[OpenClawClient] 错误输出: ${errOutput.substring(0, 500)}`);
|
|
232
|
-
resolve(`OpenClaw 调用失败: ${errOutput.substring(0, 200)}`);
|
|
350
|
+
this.log?.error(`[OpenClawClient] CLI 错误输出: ${errOutput.substring(0, 500)}`);
|
|
351
|
+
resolve(`OpenClaw CLI 调用失败: ${errOutput.substring(0, 200)}`);
|
|
233
352
|
return;
|
|
234
353
|
}
|
|
235
354
|
|