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.
- package/package.json +1 -1
- package/service/worker.js +29 -106
package/package.json
CHANGED
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
|
-
|
|
299
|
+
log.error(`[WORKER] 刷新 token 异常: ${err.message}`);
|
|
293
300
|
return false;
|
|
294
301
|
}
|
|
295
302
|
}
|
|
296
303
|
|
|
297
304
|
/**
|
|
298
305
|
* 从后端系统配置同步客服账号ID
|
|
299
|
-
*
|
|
300
|
-
*
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
}
|