claw-subagent-service 0.0.36 → 0.0.38

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/cli.js CHANGED
@@ -107,6 +107,7 @@ function installService() {
107
107
  // Linux: systemd
108
108
  const execStart = `${process.execPath} ${DAEMON_PATH}`;
109
109
  const serviceFile = `/etc/systemd/system/${SERVICE_NAME}.service`;
110
+ const pathEnv = process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
110
111
  const serviceContent = `[Unit]
111
112
  Description=OpenClaw Guard CLI Client
112
113
  After=network.target
@@ -117,6 +118,7 @@ ExecStart=${execStart}
117
118
  Restart=always
118
119
  RestartSec=10
119
120
  WorkingDirectory=${path.dirname(DAEMON_PATH)}
121
+ Environment="PATH=${pathEnv}"
120
122
 
121
123
  [Install]
122
124
  WantedBy=multi-user.target
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "description": "虾说静态服务",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -103,6 +103,7 @@ if (platform === 'win32') {
103
103
  });
104
104
  } else if (platform === 'linux') {
105
105
  const serviceFile = `/etc/systemd/system/${SERVICE_NAME}.service`;
106
+ const pathEnv = process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
106
107
  const serviceContent = `[Unit]
107
108
  Description=Node.js 静默后台服务(开机自启/崩溃自动恢复/自动更新)
108
109
  After=network.target
@@ -114,6 +115,7 @@ Restart=always
114
115
  RestartSec=10
115
116
  WorkingDirectory=${path.dirname(DAEMON_PATH)}
116
117
  Environment="SILENT_SERVICE_DIR=${ROOT}"
118
+ Environment="PATH=${pathEnv}"
117
119
 
118
120
  [Install]
119
121
  WantedBy=multi-user.target
@@ -205,6 +205,7 @@ class ServiceManager {
205
205
  // Linux 服务安装 (systemd)
206
206
  async installLinux() {
207
207
  const serviceFile = `/etc/systemd/system/${this.serviceName}.service`;
208
+ const pathEnv = process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
208
209
  const serviceContent = `[Unit]
209
210
  Description=${this.serviceDesc}
210
211
  After=network.target
@@ -216,6 +217,7 @@ ExecStart=${process.execPath} ${this.scriptPath}
216
217
  Restart=always
217
218
  RestartSec=10
218
219
  Environment=NODE_ENV=production
220
+ Environment="PATH=${pathEnv}"
219
221
 
220
222
  [Install]
221
223
  WantedBy=multi-user.target
package/service/worker.js CHANGED
@@ -3,6 +3,7 @@ const fs = require('fs');
3
3
  const path = require('path');
4
4
  const os = require('os');
5
5
  const { execSync } = require('child_process');
6
+ const axios = require('axios');
6
7
  const { createLogger } = require('./logger');
7
8
  const { RongCloudClient, MessageHandler, ensurePluginsAllow } = require('./rongcloud');
8
9
  const { RongyunMessageHandler } = require('./modules/rongyun-message-handler');
@@ -198,6 +199,53 @@ rongcloudConfig = loadRongCloudConfig();
198
199
  let rongcloudClient = null;
199
200
  let messageHandler = null;
200
201
 
202
+ /**
203
+ * 向服务端刷新融云 token
204
+ */
205
+ async function refreshRongCloudToken() {
206
+ const nodeId = rongcloudConfig?.accountId;
207
+ if (!nodeId) {
208
+ log.error('[WORKER] 无法刷新 token: 缺少 nodeId');
209
+ return false;
210
+ }
211
+
212
+ const serverUrl = process.env.DM_SERVER_URL || 'https://newsradar.dreamdt.cn/im';
213
+ try {
214
+ log.info(`[WORKER] 正在向服务端刷新 token, nodeId=${nodeId}`);
215
+ const resp = await axios.get(`${serverUrl}/api/claw/token/${nodeId}`, { timeout: 15000 });
216
+ if (resp.data?.code === 200) {
217
+ const newToken = resp.data.data?.token || resp.data.token || '';
218
+ if (!newToken) {
219
+ log.error('[WORKER] 服务端返回了空 token');
220
+ return false;
221
+ }
222
+ log.info('[WORKER] token 刷新成功');
223
+
224
+ // 更新内存配置
225
+ rongcloudConfig.token = newToken;
226
+
227
+ // 保存到 config.json
228
+ try {
229
+ if (fs.existsSync(clawBridgeConfigPath)) {
230
+ const clawConfig = JSON.parse(fs.readFileSync(clawBridgeConfigPath, 'utf8'));
231
+ clawConfig.token = newToken;
232
+ clawConfig.expiresAt = Date.now() + 7 * 24 * 60 * 60 * 1000; // 7天
233
+ fs.writeFileSync(clawBridgeConfigPath, JSON.stringify(clawConfig, null, 2));
234
+ log.info('[WORKER] 新 token 已保存到 config.json');
235
+ }
236
+ } catch (err) {
237
+ log.error(`[WORKER] 保存新 token 失败: ${err.message}`);
238
+ }
239
+ return true;
240
+ }
241
+ log.error(`[WORKER] 刷新 token 失败: ${resp.data?.message || '未知错误'}`);
242
+ return false;
243
+ } catch (err) {
244
+ log.error(`[WORKER] 刷新 token 异常: ${err.message}`);
245
+ return false;
246
+ }
247
+ }
248
+
201
249
  async function initRongCloud() {
202
250
  if (!rongcloudConfig) return;
203
251
 
@@ -233,22 +281,22 @@ async function initRongCloud() {
233
281
 
234
282
  // 包装 MessageHandler.handleMessage 以处理结构化消息
235
283
  const originalHandleMessage = messageHandler.handleMessage.bind(messageHandler);
236
-
284
+
237
285
  messageHandler.handleMessage = async (msg) => {
238
286
  // 检查是否是结构化消息
239
287
  if (msg.content && typeof msg.content === 'string') {
240
288
  try {
241
289
  const parsed = JSON.parse(msg.content);
242
-
290
+
243
291
  if (parsed.msg_type) {
244
292
  // 这是结构化消息,使用 RongyunMessageHandler 处理
245
293
  log.info(`[WORKER] 收到结构化消息: type=${parsed.msg_type}, from=${parsed.source_im_id || msg.senderUserId}`);
246
-
294
+
247
295
  // 忽略自己发送的消息
248
296
  if (parsed.source_im_id === rongcloudConfig.accountId) {
249
297
  return;
250
298
  }
251
-
299
+
252
300
  // Timestamp 校验(5分钟有效期)
253
301
  const msgTimestamp = parsed.timestamp;
254
302
  if (msgTimestamp) {
@@ -262,7 +310,7 @@ async function initRongCloud() {
262
310
  return;
263
311
  }
264
312
  }
265
-
313
+
266
314
  // 解析 content 字段(它本身可能是 JSON 字符串)
267
315
  let innerContent = parsed.content;
268
316
  if (typeof innerContent === 'string') {
@@ -272,7 +320,7 @@ async function initRongCloud() {
272
320
  // 保持字符串
273
321
  }
274
322
  }
275
-
323
+
276
324
  // 构建消息数据
277
325
  // 注意:后端发送的 command 消息中,command/command_id/request_id 在 content 字段内
278
326
  // 保留原始 content(用户消息内容),同时展开其他字段
@@ -284,7 +332,7 @@ async function initRongCloud() {
284
332
  targetId: msg.targetId,
285
333
  conversationType: msg.conversationType,
286
334
  };
287
-
335
+
288
336
  // 使用 RongyunMessageHandler 处理
289
337
  try {
290
338
  await rongyunMessageHandler.handle(messageData);
@@ -297,18 +345,30 @@ async function initRongCloud() {
297
345
  // 不是 JSON,是普通消息,继续传给原始 handler
298
346
  }
299
347
  }
300
-
348
+
301
349
  // 调用原始的 handleMessage(处理普通消息)
302
350
  return originalHandleMessage(msg);
303
351
  };
304
-
352
+
305
353
  // 添加调试日志:确认替换后的方法
306
354
  log.info('[WORKER-DEBUG] 替换后 messageHandler.handleMessage 类型: ' + typeof messageHandler.handleMessage);
307
355
 
308
- const connected = await rongcloudClient.connect(messageHandler);
356
+ let connected = await rongcloudClient.connect(messageHandler);
357
+
358
+ // 连接失败时尝试刷新 token 并重试一次
359
+ if (!connected) {
360
+ log.warn('[WORKER] 首次融云连接失败,尝试刷新 token...');
361
+ const refreshed = await refreshRongCloudToken();
362
+ if (refreshed) {
363
+ // 使用新 token 重新创建客户端并连接
364
+ rongcloudClient = new RongCloudClient(rongcloudConfig, log);
365
+ connected = await rongcloudClient.connect(messageHandler);
366
+ }
367
+ }
368
+
309
369
  if (connected) {
310
370
  log.info('[WORKER] 融云连接成功');
311
-
371
+
312
372
  // 发送 CLIENT_CONNECTED
313
373
  try {
314
374
  await messageSender.sendClientConnected();
@@ -316,21 +376,21 @@ async function initRongCloud() {
316
376
  } catch (err) {
317
377
  log.error(`[WORKER] 发送 CLIENT_CONNECTED 失败: ${err.message}`);
318
378
  }
319
-
379
+
320
380
  // 启动心跳管理器
321
381
  const heartbeatManager = new HeartbeatManager(rongcloudClient, rongcloudConfig, log);
322
382
  heartbeatManager.start(getMacAddress, getOpenClawStatus);
323
-
383
+
324
384
  // 启动仪表盘上报
325
385
  const dashboardReporter = new DashboardReporter(rongcloudClient, rongcloudConfig, log);
326
386
  dashboardReporter.start(getMacAddress);
327
-
387
+
328
388
  // 保存引用以便关闭时停止
329
389
  global.heartbeatManager = heartbeatManager;
330
390
  global.dashboardReporter = dashboardReporter;
331
-
391
+
332
392
  } else {
333
- log.error('[WORKER] 融云连接失败');
393
+ log.error('[WORKER] 融云连接失败,token 刷新后仍无法连接');
334
394
  }
335
395
  }
336
396