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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "虾说静态服务",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -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 启动(最多 15 秒)
136
+ // 等待 gateway 启动(最多 20 秒)
80
137
  let attempts = 0;
81
- const maxAttempts = 15;
138
+ const maxAttempts = 20;
82
139
  const interval = setInterval(async () => {
83
140
  attempts++;
84
- const isRunning = await checkPort(18789);
85
- if (isRunning) {
141
+ const gatewayRunning = await checkPort(18789);
142
+ const apiRunning = await checkPort(5678);
143
+ if (gatewayRunning) {
86
144
  clearInterval(interval);
87
- log?.info('[OpenClawClient] OpenClaw gateway 启动成功');
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 isRunning = await checkPort(18789);
118
- if (isRunning) {
119
- this.log?.info('[OpenClawClient] OpenClaw gateway 已在运行');
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
- // 等待最多 20
128
- for (let i = 0; i < 20; 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
- ...process.env,
303
+ ...getOpenClawEnv(),
187
304
  OPENCLAW_GATEWAY_URL: gatewayUrl,
188
- USERPROFILE: realHome,
189
- HOME: realHome,
190
305
  },
191
306
  });
192
307
 
193
- // 流式收集输出,无 maxBuffer 限制
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
- stderr += chunk.toString();
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
- // 超时兜底(20 分钟)
320
+ // 超时兜底(2 分钟)
202
321
  const timeout = setTimeout(() => {
203
322
  killed = true;
204
323
  child.kill('SIGTERM');
205
- this.log?.error('[OpenClawClient] 执行超时,强制终止');
206
- }, 1200000);
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