claw-subagent-service 0.0.26 → 0.0.28
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
CHANGED
|
@@ -122,9 +122,6 @@ class MessageHandler {
|
|
|
122
122
|
this.sendReadReceiptFn(msg).catch(() => {});
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
// 先回复"正在处理",让用户知道消息已被接收
|
|
126
|
-
this.sendFn(targetId, '🤖 正在思考中,请稍候...', msg.conversationType).catch(() => {});
|
|
127
|
-
|
|
128
125
|
// 后台执行 openclaw,不阻塞消息队列
|
|
129
126
|
this.openclawClient.chat(msg.content, msg.senderUserId)
|
|
130
127
|
.then(reply => {
|
|
@@ -33,6 +33,98 @@ function getRealHomeDir() {
|
|
|
33
33
|
return homeDir;
|
|
34
34
|
}
|
|
35
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
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 尝试读取 OpenClaw 的 gateway token
|
|
98
|
+
* openclaw agent 连接 gateway 需要认证,token 通常存储在配置文件中
|
|
99
|
+
*/
|
|
100
|
+
function getGatewayToken() {
|
|
101
|
+
const homeDir = getRealHomeDir();
|
|
102
|
+
const possibleFiles = [
|
|
103
|
+
path.join(homeDir, '.openclaw', 'openclaw.json'),
|
|
104
|
+
path.join(homeDir, '.openclaw', 'config.json'),
|
|
105
|
+
path.join(homeDir, '.openclaw', 'tools.json'),
|
|
106
|
+
path.join(homeDir, '.openclaw', 'settings.json'),
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
for (const filePath of possibleFiles) {
|
|
110
|
+
try {
|
|
111
|
+
if (fs.existsSync(filePath)) {
|
|
112
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
113
|
+
const config = JSON.parse(content);
|
|
114
|
+
// 可能的 token 字段名
|
|
115
|
+
const token = config.gatewayToken || config.token || config.apiKey || config.api_key || config.password;
|
|
116
|
+
if (token) {
|
|
117
|
+
return String(token);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
// 忽略读取/解析错误
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
36
128
|
/**
|
|
37
129
|
* 检测端口是否监听
|
|
38
130
|
*/
|
|
@@ -68,24 +160,20 @@ function startOpenClawGateway(log) {
|
|
|
68
160
|
windowsHide: true,
|
|
69
161
|
detached: true,
|
|
70
162
|
stdio: 'ignore',
|
|
71
|
-
env:
|
|
72
|
-
...process.env,
|
|
73
|
-
USERPROFILE: getRealHomeDir(),
|
|
74
|
-
HOME: getRealHomeDir(),
|
|
75
|
-
},
|
|
163
|
+
env: getOpenClawEnv(),
|
|
76
164
|
});
|
|
77
165
|
|
|
78
166
|
child.unref();
|
|
79
167
|
|
|
80
|
-
// 等待 gateway 启动(最多
|
|
168
|
+
// 等待 gateway 启动(最多 20 秒)
|
|
81
169
|
let attempts = 0;
|
|
82
|
-
const maxAttempts =
|
|
170
|
+
const maxAttempts = 20;
|
|
83
171
|
const interval = setInterval(async () => {
|
|
84
172
|
attempts++;
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
173
|
+
const gatewayRunning = await checkPort(18789);
|
|
174
|
+
if (gatewayRunning) {
|
|
87
175
|
clearInterval(interval);
|
|
88
|
-
log?.info('[OpenClawClient] OpenClaw gateway 启动成功');
|
|
176
|
+
log?.info('[OpenClawClient] OpenClaw gateway 启动成功 (18789)');
|
|
89
177
|
resolve(true);
|
|
90
178
|
} else if (attempts >= maxAttempts) {
|
|
91
179
|
clearInterval(interval);
|
|
@@ -115,9 +203,10 @@ class OpenClawClient {
|
|
|
115
203
|
async ensureGatewayRunning() {
|
|
116
204
|
if (this.gatewayStarted) return true;
|
|
117
205
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
206
|
+
const gatewayRunning = await checkPort(18789);
|
|
207
|
+
|
|
208
|
+
if (gatewayRunning) {
|
|
209
|
+
this.log?.info('[OpenClawClient] OpenClaw gateway 已在运行 (18789)');
|
|
121
210
|
this.gatewayStarted = true;
|
|
122
211
|
return true;
|
|
123
212
|
}
|
|
@@ -125,8 +214,8 @@ class OpenClawClient {
|
|
|
125
214
|
// 避免并发启动
|
|
126
215
|
if (this.gatewayStarting) {
|
|
127
216
|
this.log?.info('[OpenClawClient] gateway 正在启动中,等待...');
|
|
128
|
-
// 等待最多
|
|
129
|
-
for (let i = 0; i <
|
|
217
|
+
// 等待最多 25 秒
|
|
218
|
+
for (let i = 0; i < 25; i++) {
|
|
130
219
|
await new Promise(r => setTimeout(r, 1000));
|
|
131
220
|
if (await checkPort(18789)) {
|
|
132
221
|
this.gatewayStarted = true;
|
|
@@ -209,41 +298,53 @@ class OpenClawClient {
|
|
|
209
298
|
.replace(/\n/g, ' ')
|
|
210
299
|
.replace(/\r/g, ' ');
|
|
211
300
|
|
|
212
|
-
const gatewayUrl = 'http://127.0.0.1:5678';
|
|
213
301
|
const realHome = getRealHomeDir();
|
|
214
302
|
this.log?.info(`[OpenClawClient] 使用用户目录: ${realHome}`);
|
|
215
303
|
|
|
304
|
+
// 尝试读取 gateway token,解决 SYSTEM 账户下认证问题
|
|
305
|
+
const gatewayToken = getGatewayToken();
|
|
306
|
+
if (gatewayToken) {
|
|
307
|
+
this.log?.info('[OpenClawClient] 已读取到 gateway token');
|
|
308
|
+
}
|
|
309
|
+
|
|
216
310
|
const args = ['agent', '-m', escapedMessage, '--session-id', sessionId];
|
|
217
|
-
|
|
311
|
+
if (gatewayToken) {
|
|
312
|
+
args.push('--token', gatewayToken);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
this.log?.info(`[OpenClawClient] 执行: openclaw ${args.map(a => a.includes(' ') ? `"${a}"` : a).join(' ')}`);
|
|
218
316
|
|
|
219
317
|
return new Promise((resolve) => {
|
|
220
318
|
let stdout = '';
|
|
221
319
|
let stderr = '';
|
|
222
320
|
let killed = false;
|
|
223
321
|
|
|
322
|
+
// 关键:不设置 OPENCLAW_GATEWAY_URL,避免触发 "gateway url override requires explicit credentials"
|
|
323
|
+
// 让 openclaw agent 通过默认方式自动发现本地 gateway
|
|
224
324
|
const child = spawn('openclaw', args, {
|
|
225
325
|
shell: true,
|
|
226
326
|
windowsHide: true,
|
|
227
|
-
env:
|
|
228
|
-
...process.env,
|
|
229
|
-
OPENCLAW_GATEWAY_URL: gatewayUrl,
|
|
230
|
-
USERPROFILE: realHome,
|
|
231
|
-
HOME: realHome,
|
|
232
|
-
},
|
|
327
|
+
env: getOpenClawEnv(),
|
|
233
328
|
});
|
|
234
329
|
|
|
330
|
+
this.log?.info(`[OpenClawClient] CLI 子进程 PID=${child.pid}`);
|
|
331
|
+
|
|
235
332
|
child.stdout.on('data', (chunk) => {
|
|
236
333
|
stdout += chunk.toString();
|
|
237
334
|
});
|
|
238
335
|
child.stderr.on('data', (chunk) => {
|
|
239
|
-
|
|
336
|
+
const text = chunk.toString();
|
|
337
|
+
stderr += text;
|
|
338
|
+
// 实时记录 stderr,方便调试卡死问题
|
|
339
|
+
this.log?.info(`[OpenClawClient] CLI stderr: ${text.trim().substring(0, 300)}`);
|
|
240
340
|
});
|
|
241
341
|
|
|
342
|
+
// 超时兜底(30 秒)
|
|
242
343
|
const timeout = setTimeout(() => {
|
|
243
344
|
killed = true;
|
|
244
345
|
child.kill('SIGTERM');
|
|
245
|
-
this.log?.error('[OpenClawClient] CLI
|
|
246
|
-
},
|
|
346
|
+
this.log?.error('[OpenClawClient] CLI 执行超时(30秒),强制终止');
|
|
347
|
+
}, 30000);
|
|
247
348
|
|
|
248
349
|
child.on('error', (err) => {
|
|
249
350
|
clearTimeout(timeout);
|
|
@@ -259,7 +360,7 @@ class OpenClawClient {
|
|
|
259
360
|
clearTimeout(timeout);
|
|
260
361
|
|
|
261
362
|
if (killed) {
|
|
262
|
-
resolve('OpenClaw
|
|
363
|
+
resolve('OpenClaw 响应超时(30秒),请检查 openclaw 服务状态');
|
|
263
364
|
return;
|
|
264
365
|
}
|
|
265
366
|
|
|
@@ -269,7 +370,7 @@ class OpenClawClient {
|
|
|
269
370
|
if (code !== 0) {
|
|
270
371
|
const errOutput = stderr || stdout || '';
|
|
271
372
|
this.log?.error(`[OpenClawClient] CLI 错误输出: ${errOutput.substring(0, 500)}`);
|
|
272
|
-
resolve(`OpenClaw
|
|
373
|
+
resolve(`OpenClaw 调用失败: ${errOutput.substring(0, 200)}`);
|
|
273
374
|
return;
|
|
274
375
|
}
|
|
275
376
|
|