claw-subagent-service 0.0.170 → 0.0.173

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/service/worker.js +29 -106
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.170",
3
+ "version": "0.0.173",
4
4
  "description": "虾说智能助手",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/service/worker.js CHANGED
@@ -135,7 +135,7 @@ try {
135
135
  process.cwd();
136
136
  } catch (e) {
137
137
  if (e.code === 'ENOENT') {
138
- try { process.chdir(os.tmpdir()); } catch {}
138
+ try { process.chdir(os.tmpdir()); } catch { }
139
139
  }
140
140
  }
141
141
 
@@ -224,7 +224,6 @@ function loadRongCloudConfig() {
224
224
 
225
225
  if (!config.appKey) {
226
226
  config.appKey = process.env.DM_APP_KEY || 'bmdehs6pbyyks';
227
- log.info(`[WORKER] 使用默认 appKey: ${config.appKey}`);
228
227
  }
229
228
 
230
229
  // 设置默认心跳间隔为20秒
@@ -232,7 +231,7 @@ function loadRongCloudConfig() {
232
231
  config.heartbeatInterval = 20;
233
232
  }
234
233
 
235
- log.info(`[WORKER] 最终 apiBaseUrl: ${config.apiBaseUrl}`);
234
+ // log.info(`[WORKER] 最终 apiBaseUrl: ${config.apiBaseUrl}`);
236
235
 
237
236
  if (config.token && config.accountId) {
238
237
  return config;
@@ -263,6 +262,7 @@ async function refreshRongCloudToken() {
263
262
  const resp = await axios.get(`${serverUrl}/api/claw/token/${nodeId}`, { timeout: 15000 });
264
263
  if (resp.data?.code === 200) {
265
264
  const newToken = resp.data.data?.token || resp.data.token || '';
265
+ const newAppKey = resp.data.data?.app_key || resp.data.app_key || '';
266
266
  if (!newToken) {
267
267
  log.error('[WORKER] 服务端返回了空 token');
268
268
  return false;
@@ -271,12 +271,19 @@ async function refreshRongCloudToken() {
271
271
 
272
272
  // 更新内存配置
273
273
  rongcloudConfig.token = newToken;
274
+ if (newAppKey) {
275
+ rongcloudConfig.appKey = newAppKey;
276
+ log.info(`[WORKER] appKey 已更新: ${newAppKey}`);
277
+ }
274
278
 
275
279
  // 保存到 config.json
276
280
  try {
277
281
  if (fs.existsSync(clawBridgeConfigPath)) {
278
282
  const clawConfig = JSON.parse(fs.readFileSync(clawBridgeConfigPath, 'utf8'));
279
283
  clawConfig.token = newToken;
284
+ if (newAppKey) {
285
+ clawConfig.appKey = newAppKey;
286
+ }
280
287
  clawConfig.expiresAt = Date.now() + 7 * 24 * 60 * 60 * 1000; // 7天
281
288
  fs.writeFileSync(clawBridgeConfigPath, JSON.stringify(clawConfig, null, 2));
282
289
  log.info('[WORKER] 新 token 已保存到 config.json');
@@ -289,107 +296,23 @@ async function refreshRongCloudToken() {
289
296
  log.error(`[WORKER] 刷新 token 失败: ${resp.data?.message || '未知错误'}`);
290
297
  return false;
291
298
  } catch (err) {
292
- log.error(`[WORKER] 刷新 token 异常: ${err.message}`);
299
+ log.error(`[WORKER] 刷新 token 异常: ${err.message}`);
293
300
  return false;
294
301
  }
295
302
  }
296
303
 
297
304
  /**
298
305
  * 从后端系统配置同步客服账号ID
299
- * 如果数据库配置的 customer_service_account_id 与当前 accountId 不一致,
300
- * 则获取新账号的融云 token 并更新本地配置,使前端消息能正确送达
301
- *
302
- * 设计原理:
303
- * - 客服账号由管理员在系统配置表(im_system_config)中管理,支持频繁更新
304
- * - silent-service 本地 config.json 的 nodeId 由 clawmessenger 注册时决定
305
- * - 两者可能不一致,导致前端将消息发给 ID-A,但 silent-service 在 ID-B 上监听
306
- * - 此函数在启动时拉取数据库配置,自动对齐
306
+ *
307
+ * 重要:节点服务保持独立,不强制切换为客服账号
308
+ * 客服账号仅用于专门的客服服务实例,不应影响普通节点服务
307
309
  *
308
310
  * @returns {Promise<boolean>} 配置是否已更新
309
311
  */
310
312
  async function syncCustomerServiceAccountId() {
311
- if (!rongcloudConfig?.apiBaseUrl) {
312
- log.warn('[WORKER] 无法同步客服账号: 缺少 apiBaseUrl');
313
- return false;
314
- }
315
-
316
- try {
317
- const resp = await axios.get(
318
- `${rongcloudConfig.apiBaseUrl}/im/api/system/config/customer_service_account_id`,
319
- { timeout: 10000 }
320
- );
321
-
322
- if (resp.data?.code === 200 && resp.data?.data?.value) {
323
- const csAccountId = resp.data.data.value;
324
- const currentAccountId = rongcloudConfig.accountId;
325
-
326
- if (csAccountId === currentAccountId) {
327
- log.info(`[WORKER] 客服账号一致: ${csAccountId},无需变更`);
328
- return false;
329
- }
330
-
331
- log.info(`[WORKER] 客服账号不一致: 当前=${currentAccountId}, 数据库配置=${csAccountId},正在同步...`);
332
-
333
- // 获取新账号的融云 token(节点必须已注册)
334
- // 用独立 try-catch 包裹,与外部"配置不存在"的区分开
335
- try {
336
- const serverUrl = process.env.DM_SERVER_URL || 'https://newsradar.dreamdt.cn/im';
337
- const tokenResp = await axios.get(
338
- `${serverUrl}/api/claw/token/${csAccountId}`,
339
- { timeout: 15000 }
340
- );
341
-
342
- if (tokenResp.data?.code === 200) {
343
- const newToken = tokenResp.data.data?.token || tokenResp.data.token || '';
344
- if (newToken) {
345
- log.info(`[WORKER] 获取客服账号 token 成功: ${csAccountId}`);
346
-
347
- // 更新内存配置(影响后续融云连接)
348
- rongcloudConfig.accountId = csAccountId;
349
- rongcloudConfig.token = newToken;
350
-
351
- // 持久化到本地 config.json(确保重启后仍使用新配置)
352
- try {
353
- if (fs.existsSync(clawBridgeConfigPath)) {
354
- const clawConfig = JSON.parse(fs.readFileSync(clawBridgeConfigPath, 'utf8'));
355
- clawConfig.nodeId = csAccountId;
356
- clawConfig.token = newToken;
357
- clawConfig.expiresAt = Date.now() + 7 * 24 * 60 * 60 * 1000; // 7天
358
- fs.writeFileSync(clawBridgeConfigPath, JSON.stringify(clawConfig, null, 2));
359
- log.info(`[WORKER] 客服账号已持久化到 config.json: ${csAccountId}`);
360
- }
361
- } catch (err) {
362
- log.error(`[WORKER] 持久化客服账号到 config.json 失败: ${err.message}`);
363
- }
364
-
365
- return true;
366
- }
367
- }
368
- } catch (tokenErr) {
369
- if (tokenErr.response?.status === 404) {
370
- log.warn(`[WORKER] 客服账号 ${csAccountId} 未注册为 claw 节点,无法获取 token。请先使用 clawmessenger 注册该账号`);
371
- } else {
372
- log.warn(`[WORKER] 获取客服账号 token 失败: ${tokenErr.message}`);
373
- }
374
- }
375
-
376
- log.warn(`[WORKER] 保持当前配置: ${currentAccountId}`);
377
- return false;
378
- }
379
-
380
- // API 返回了非 200(如配置不存在时 404),静默忽略
381
- return false;
382
- } catch (err) {
383
- // 网络错误在后端未就绪时常见,静默失败不影响启动
384
- if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT' || err.code === 'ENOTFOUND') {
385
- log.warn(`[WORKER] 无法连接后端同步客服账号: ${err.message},保持当前配置`);
386
- } else if (err.response?.status === 404) {
387
- log.info('[WORKER] 客服账号未在系统配置中设置,使用当前配置');
388
- } else {
389
- log.error(`[WORKER] 同步客服账号异常: ${err.message}`);
390
- }
391
- return false;
392
- }
313
+ // 节点服务保持独立,不切换客服账号
314
+ log.info(`[WORKER] 节点服务保持独立账号: ${rongcloudConfig?.accountId || 'unknown'},不切换客服账号`);
315
+ return false;
393
316
  }
394
317
 
395
318
  async function initRongCloud() {
@@ -402,7 +325,7 @@ async function initRongCloud() {
402
325
  const configChanged = await syncCustomerServiceAccountId();
403
326
  if (configChanged) {
404
327
  log.info('[WORKER] 客服账号已切换到数据库配置的 ID,将使用新配置连接融云');
405
- }
328
+ }
406
329
  log.info(`[WORKER] 代码版本特征: isOffLineMessage-pass-through, messageDirection-log, addEventListener-exclusive`);
407
330
 
408
331
  // 启动 opencode 服务(与桌面客户端对齐)
@@ -443,7 +366,7 @@ async function initRongCloud() {
443
366
  // 融云 SDK 对自定义消息可能直接返回对象而非 JSON 字符串
444
367
  if (msg.content) {
445
368
  let parsed = null;
446
-
369
+
447
370
  if (typeof msg.content === 'string') {
448
371
  // 字符串类型:尝试 JSON 解析
449
372
  try {
@@ -568,7 +491,7 @@ async function shutdownRongCloud() {
568
491
  if (global.dashboardReporter) {
569
492
  global.dashboardReporter.stop();
570
493
  }
571
-
494
+
572
495
  if (rongcloudClient) {
573
496
  // 发送 CLIENT_DISCONNECTED
574
497
  try {
@@ -578,11 +501,11 @@ async function shutdownRongCloud() {
578
501
  } catch (err) {
579
502
  log.error(`[WORKER] 发送 CLIENT_DISCONNECTED 失败: ${err.message}`);
580
503
  }
581
-
504
+
582
505
  await rongcloudClient.disconnect();
583
506
  log.info('[WORKER] 融云已断开');
584
507
  }
585
-
508
+
586
509
  // 停止 opencode 服务
587
510
  stopOpencodeService(log);
588
511
  }
@@ -644,7 +567,7 @@ server.on('error', (err) => {
644
567
  // 延迟 3 秒后重试,给进程退出和端口释放留出足够时间
645
568
  setTimeout(() => {
646
569
  log.info(`[WORKER] 重新尝试监听端口 ${PORT}...`);
647
- server.close(() => {});
570
+ server.close(() => { });
648
571
  server.listen(PORT, HOST);
649
572
  }, 3000);
650
573
  return;
@@ -681,20 +604,20 @@ async function gracefulShutdown(signal) {
681
604
  return;
682
605
  }
683
606
  isShuttingDown = true;
684
-
607
+
685
608
  log.info(`[WORKER] 收到 ${signal} 信号,开始优雅退出...`);
686
-
609
+
687
610
  try {
688
611
  await shutdownRongCloud();
689
612
  } catch (err) {
690
613
  log.error(`[WORKER] 关闭融云异常: ${err.message}`);
691
614
  }
692
-
615
+
693
616
  // 关闭 HTTP 服务
694
617
  server.close(() => {
695
618
  log.info('[WORKER] HTTP 服务已关闭');
696
619
  });
697
-
620
+
698
621
  // 给 3 秒时间完成关闭操作
699
622
  setTimeout(() => {
700
623
  log.info('[WORKER] 退出进程');
@@ -739,7 +662,7 @@ process.on('unhandledRejection', async (reason) => {
739
662
 
740
663
  // 拦截 process.exit 以定位调用来源
741
664
  const originalExit = process.exit;
742
- process.exit = function(code) {
665
+ process.exit = function (code) {
743
666
  const stack = new Error('process.exit called from:').stack;
744
667
  log.error(`[WORKER] process.exit(${code}) 被调用:\n${stack}`);
745
668
  originalExit.call(process, code);
@@ -752,7 +675,7 @@ process.on('exit', (code) => {
752
675
  // 同步发送,因为 exit 事件不支持异步
753
676
  try {
754
677
  const messageSender = new RongyunMessageSender(rongcloudClient, rongcloudConfig, log);
755
- messageSender.sendClientDisconnected().catch(() => {});
678
+ messageSender.sendClientDisconnected().catch(() => { });
756
679
  } catch (e) {
757
680
  // 忽略错误
758
681
  }