claw-subagent-service 0.0.40 → 0.0.41
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/daemon.js +31 -4
- package/service/rongcloud/rongcloud-client.js +3 -2
- package/service/worker.js +24 -6
package/package.json
CHANGED
package/service/daemon.js
CHANGED
|
@@ -119,11 +119,38 @@ function freePortIfNeeded(port) {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
} else {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
// Linux / macOS:优先 lsof,兜底 fuser / ss / netstat / pkill
|
|
123
|
+
let pid = 0;
|
|
124
|
+
const commands = [
|
|
125
|
+
`lsof -i :${port} -t 2>/dev/null`,
|
|
126
|
+
`fuser ${port}/tcp 2>/dev/null`,
|
|
127
|
+
`ss -tlnp 2>/dev/null | grep -oP 'pid=\\K[0-9]+'`,
|
|
128
|
+
`netstat -tlnp 2>/dev/null | grep ":${port} " | awk '{print $7}' | cut -d'/' -f1`,
|
|
129
|
+
];
|
|
130
|
+
for (const cmd of commands) {
|
|
131
|
+
try {
|
|
132
|
+
const out = execSync(cmd, { encoding: 'utf8', timeout: 5000 }).trim();
|
|
133
|
+
const firstLine = out.split(/\r?\n/)[0];
|
|
134
|
+
const candidate = parseInt(firstLine, 10);
|
|
135
|
+
if (candidate && candidate > 0 && candidate !== currentWorkerPid && candidate !== process.pid) {
|
|
136
|
+
pid = candidate;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
} catch { /* 命令不可用,继续下一个兜底 */ }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (pid) {
|
|
125
143
|
log.warn(`[DAEMON] 端口 ${port} 被进程 ${pid} 占用,正在释放...`);
|
|
126
|
-
process.kill(pid, 'SIGKILL');
|
|
144
|
+
try { process.kill(pid, 'SIGKILL'); } catch (e) {
|
|
145
|
+
log.warn(`[DAEMON] 终止进程 ${pid} 失败: ${e.message}`);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
// 所有命令都不可用,尝试按脚本名批量杀掉残留进程
|
|
149
|
+
try {
|
|
150
|
+
execSync('pkill -9 -f "daemon.js" 2>/dev/null || true', { timeout: 5000 });
|
|
151
|
+
execSync('pkill -9 -f "worker.js" 2>/dev/null || true', { timeout: 5000 });
|
|
152
|
+
log.warn(`[DAEMON] 已尝试按脚本名释放端口 ${port}`);
|
|
153
|
+
} catch { /* 忽略 */ }
|
|
127
154
|
}
|
|
128
155
|
}
|
|
129
156
|
} catch { /* 端口已被释放或无法查询 */ }
|
|
@@ -128,11 +128,12 @@ class RongCloudClient {
|
|
|
128
128
|
try {
|
|
129
129
|
const msgType = message.messageType;
|
|
130
130
|
let rawContent = message.content;
|
|
131
|
-
|
|
131
|
+
// 融云 SDK 中 mentionedInfo 通常在消息根级别
|
|
132
|
+
let mentionedInfo = message.mentionedInfo || null;
|
|
132
133
|
|
|
133
134
|
// 自定义消息 content 可能是对象,提取文本内容并保留 mentionedInfo
|
|
134
135
|
if (rawContent && typeof rawContent === 'object') {
|
|
135
|
-
mentionedInfo = rawContent.mentionedInfo || null;
|
|
136
|
+
mentionedInfo = mentionedInfo || rawContent.mentionedInfo || null;
|
|
136
137
|
rawContent = rawContent.content || rawContent.text || JSON.stringify(rawContent);
|
|
137
138
|
}
|
|
138
139
|
|
package/service/worker.js
CHANGED
|
@@ -65,8 +65,8 @@ function findPidOnPort(port) {
|
|
|
65
65
|
for (const cmd of commands) {
|
|
66
66
|
try {
|
|
67
67
|
const out = execSync(cmd, { encoding: 'utf8', timeout: 5000 }).trim();
|
|
68
|
-
const
|
|
69
|
-
if (!isNaN(
|
|
68
|
+
const candidate = parseInt(out.split('\n')[0], 10);
|
|
69
|
+
if (!isNaN(candidate) && candidate > 0 && candidate !== process.pid) return candidate;
|
|
70
70
|
} catch { continue; }
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -95,7 +95,18 @@ function forceKill(pid) {
|
|
|
95
95
|
function ensurePortFree(port) {
|
|
96
96
|
for (let i = 0; i < 3; i++) {
|
|
97
97
|
const pid = findPidOnPort(port);
|
|
98
|
-
if (!pid)
|
|
98
|
+
if (!pid) {
|
|
99
|
+
// 端口查询工具都不可用但端口仍可能被占用,按脚本名兜底清理一次
|
|
100
|
+
if (i === 0 && process.platform !== 'win32') {
|
|
101
|
+
try {
|
|
102
|
+
execSync('pkill -9 -f "daemon.js" 2>/dev/null || true', { timeout: 5000 });
|
|
103
|
+
execSync('pkill -9 -f "worker.js" 2>/dev/null || true', { timeout: 5000 });
|
|
104
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2000);
|
|
105
|
+
} catch { /* 忽略 */ }
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
99
110
|
log.warn(`[WORKER] 端口 ${port} 被进程 ${pid} 占用,正在释放...`);
|
|
100
111
|
forceKill(pid);
|
|
101
112
|
// 同步等待端口释放(最多 1.5s)
|
|
@@ -479,16 +490,23 @@ server.on('error', (err) => {
|
|
|
479
490
|
log.error(`[WORKER] 端口 ${PORT} 被占用,尝试释放并重启监听...`);
|
|
480
491
|
// 尝试杀死占用进程后重试
|
|
481
492
|
const pid = findPidOnPort(PORT);
|
|
482
|
-
if (pid) {
|
|
493
|
+
if (pid && pid !== process.pid) {
|
|
483
494
|
log.warn(`[WORKER] 发现占用进程 ${pid},强制终止...`);
|
|
484
495
|
forceKill(pid);
|
|
496
|
+
} else if (!pid) {
|
|
497
|
+
// 所有端口查询工具都不可用(常见于精简 Docker 镜像),按脚本名兜底清理
|
|
498
|
+
log.warn('[WORKER] 无法查询端口占用进程,尝试按脚本名清理残留...');
|
|
499
|
+
try {
|
|
500
|
+
execSync('pkill -9 -f "daemon.js" 2>/dev/null || true', { timeout: 5000 });
|
|
501
|
+
execSync('pkill -9 -f "worker.js" 2>/dev/null || true', { timeout: 5000 });
|
|
502
|
+
} catch { /* 忽略 */ }
|
|
485
503
|
}
|
|
486
|
-
// 延迟
|
|
504
|
+
// 延迟 3 秒后重试,给进程退出和端口释放留出足够时间
|
|
487
505
|
setTimeout(() => {
|
|
488
506
|
log.info(`[WORKER] 重新尝试监听端口 ${PORT}...`);
|
|
489
507
|
server.close(() => {});
|
|
490
508
|
server.listen(PORT, HOST);
|
|
491
|
-
},
|
|
509
|
+
}, 3000);
|
|
492
510
|
return;
|
|
493
511
|
}
|
|
494
512
|
log.error(`[WORKER] HTTP 服务错误: ${err.message}`);
|