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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "虾说静态服务",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -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 启动(最多 15 秒)
168
+ // 等待 gateway 启动(最多 20 秒)
81
169
  let attempts = 0;
82
- const maxAttempts = 15;
170
+ const maxAttempts = 20;
83
171
  const interval = setInterval(async () => {
84
172
  attempts++;
85
- const isRunning = await checkPort(18789);
86
- if (isRunning) {
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 isRunning = await checkPort(18789);
119
- if (isRunning) {
120
- this.log?.info('[OpenClawClient] OpenClaw gateway 已在运行');
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
- // 等待最多 20
129
- for (let i = 0; i < 20; 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
- this.log?.info(`[OpenClawClient] 执行: openclaw ${args.join(' ')}`);
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
- stderr += chunk.toString();
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
- }, 1200000);
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 CLI 响应超时');
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 CLI 调用失败: ${errOutput.substring(0, 200)}`);
373
+ resolve(`OpenClaw 调用失败: ${errOutput.substring(0, 200)}`);
273
374
  return;
274
375
  }
275
376