claw-subagent-service 0.0.26 → 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.26",
3
+ "version": "0.0.27",
4
4
  "description": "虾说静态服务",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -33,6 +33,66 @@ 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
+
36
96
  /**
37
97
  * 检测端口是否监听
38
98
  */
@@ -68,24 +128,21 @@ function startOpenClawGateway(log) {
68
128
  windowsHide: true,
69
129
  detached: true,
70
130
  stdio: 'ignore',
71
- env: {
72
- ...process.env,
73
- USERPROFILE: getRealHomeDir(),
74
- HOME: getRealHomeDir(),
75
- },
131
+ env: getOpenClawEnv(),
76
132
  });
77
133
 
78
134
  child.unref();
79
135
 
80
- // 等待 gateway 启动(最多 15 秒)
136
+ // 等待 gateway 启动(最多 20 秒)
81
137
  let attempts = 0;
82
- const maxAttempts = 15;
138
+ const maxAttempts = 20;
83
139
  const interval = setInterval(async () => {
84
140
  attempts++;
85
- const isRunning = await checkPort(18789);
86
- if (isRunning) {
141
+ const gatewayRunning = await checkPort(18789);
142
+ const apiRunning = await checkPort(5678);
143
+ if (gatewayRunning) {
87
144
  clearInterval(interval);
88
- log?.info('[OpenClawClient] OpenClaw gateway 启动成功');
145
+ log?.info(`[OpenClawClient] OpenClaw gateway 启动成功 (18789),API 状态: ${apiRunning ? '就绪' : '未就绪'}`);
89
146
  resolve(true);
90
147
  } else if (attempts >= maxAttempts) {
91
148
  clearInterval(interval);
@@ -115,18 +172,36 @@ class OpenClawClient {
115
172
  async ensureGatewayRunning() {
116
173
  if (this.gatewayStarted) return true;
117
174
 
118
- const isRunning = await checkPort(18789);
119
- if (isRunning) {
120
- 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 均已就绪');
121
180
  this.gatewayStarted = true;
122
181
  return true;
123
182
  }
124
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
+
125
200
  // 避免并发启动
126
201
  if (this.gatewayStarting) {
127
202
  this.log?.info('[OpenClawClient] gateway 正在启动中,等待...');
128
- // 等待最多 20
129
- for (let i = 0; i < 20; i++) {
203
+ // 等待最多 25
204
+ for (let i = 0; i < 25; i++) {
130
205
  await new Promise(r => setTimeout(r, 1000));
131
206
  if (await checkPort(18789)) {
132
207
  this.gatewayStarted = true;
@@ -225,25 +300,29 @@ class OpenClawClient {
225
300
  shell: true,
226
301
  windowsHide: true,
227
302
  env: {
228
- ...process.env,
303
+ ...getOpenClawEnv(),
229
304
  OPENCLAW_GATEWAY_URL: gatewayUrl,
230
- USERPROFILE: realHome,
231
- HOME: realHome,
232
305
  },
233
306
  });
234
307
 
308
+ this.log?.info(`[OpenClawClient] CLI 子进程 PID=${child.pid}`);
309
+
235
310
  child.stdout.on('data', (chunk) => {
236
311
  stdout += chunk.toString();
237
312
  });
238
313
  child.stderr.on('data', (chunk) => {
239
- 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)}`);
240
318
  });
241
319
 
320
+ // 超时兜底(2 分钟)
242
321
  const timeout = setTimeout(() => {
243
322
  killed = true;
244
323
  child.kill('SIGTERM');
245
- this.log?.error('[OpenClawClient] CLI 执行超时,强制终止');
246
- }, 1200000);
324
+ this.log?.error('[OpenClawClient] CLI 执行超时(2分钟),强制终止');
325
+ }, 120000);
247
326
 
248
327
  child.on('error', (err) => {
249
328
  clearTimeout(timeout);