claw-subagent-service 0.0.38 → 0.0.39
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/README.md +411 -60
- package/cli.js +16 -1
- package/command/linux/restart.sh +138 -21
- package/command/linux/start.sh +117 -24
- package/command/linux/status.sh +172 -43
- package/command/linux/stop.sh +143 -36
- package/package.json +1 -1
- package/scripts/post-install.js +15 -0
- package/service/daemon/clawsubagentservice.exe +0 -0
- package/service/daemon/clawsubagentservice.exe.config +6 -0
- package/service/daemon/clawsubagentservice.xml +33 -0
- package/service/modules/opencode-service.js +139 -66
- package/service/worker.js +18 -6
package/command/linux/stop.sh
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# OpenClaw 服务停止脚本
|
|
4
4
|
# 用法: ./stop.sh [选项]
|
|
5
|
+
# 支持 systemd 和 Docker(无 systemd)双模式
|
|
5
6
|
|
|
6
7
|
set -e
|
|
7
8
|
|
|
@@ -27,12 +28,146 @@ log_error() {
|
|
|
27
28
|
# 服务名称
|
|
28
29
|
SERVICE_NAME="openclaw-gateway.service"
|
|
29
30
|
|
|
30
|
-
#
|
|
31
|
-
|
|
31
|
+
# 检测是否在 Docker 环境(无 systemd)
|
|
32
|
+
is_docker() {
|
|
33
|
+
if ! command -v systemctl &>/dev/null; then
|
|
34
|
+
return 0 # 无 systemctl,认为是 Docker
|
|
35
|
+
fi
|
|
36
|
+
return 1
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# 端口号
|
|
40
|
+
PORT="18789"
|
|
41
|
+
|
|
42
|
+
# 检查端口是否监听
|
|
43
|
+
check_port() {
|
|
44
|
+
local port=$1
|
|
45
|
+
if command -v netstat &>/dev/null; then
|
|
46
|
+
netstat -tnlp 2>/dev/null | grep -q ":$port "
|
|
47
|
+
else
|
|
48
|
+
# 降级:直接检查进程
|
|
49
|
+
[ -n "$(get_openclaw_pid)" ]
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# 获取 openclaw 进程 PID
|
|
54
|
+
get_openclaw_pid() {
|
|
55
|
+
# 只通过端口查找进程(最可靠,避免僵尸进程和误匹配)
|
|
56
|
+
if command -v netstat &>/dev/null; then
|
|
57
|
+
local pid
|
|
58
|
+
pid=$(netstat -tnlp 2>/dev/null | grep ":18789 " | head -1 | awk '{print $7}' | cut -d'/' -f1)
|
|
59
|
+
if [ -n "$pid" ]; then
|
|
60
|
+
echo "$pid"
|
|
61
|
+
return
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# 没有监听端口,返回空(不再使用 pgrep 避免误匹配)
|
|
66
|
+
echo ""
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Docker 模式:停止服务
|
|
70
|
+
stop_docker() {
|
|
71
|
+
local pid
|
|
72
|
+
pid=$(get_openclaw_pid)
|
|
73
|
+
|
|
74
|
+
# 检查服务状态
|
|
75
|
+
log_info "检查 OpenClaw 服务状态..."
|
|
76
|
+
if [ -z "$pid" ]; then
|
|
77
|
+
log_warn "OpenClaw 服务未在运行。"
|
|
78
|
+
exit 0
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
log_info "OpenClaw 服务正在运行(PID: $pid),准备停止..."
|
|
82
|
+
|
|
83
|
+
# 停止服务:先发送 SIGTERM(优雅停止)
|
|
84
|
+
log_info "正在停止 OpenClaw 服务..."
|
|
85
|
+
kill "$pid" &>/dev/null || true
|
|
86
|
+
|
|
87
|
+
# 等待服务完全停止(最多 10 秒)
|
|
88
|
+
local elapsed=0
|
|
89
|
+
while [ $elapsed -lt 10 ]; do
|
|
90
|
+
if [ -z "$(get_openclaw_pid)" ]; then
|
|
91
|
+
log_info "OpenClaw 服务停止成功!"
|
|
92
|
+
log_info "服务已成功停止。"
|
|
93
|
+
log_info "Success"
|
|
94
|
+
exit 0
|
|
95
|
+
fi
|
|
96
|
+
sleep 1
|
|
97
|
+
elapsed=$((elapsed + 1))
|
|
98
|
+
done
|
|
99
|
+
|
|
100
|
+
# 如果还在运行,强制停止(SIGKILL)
|
|
101
|
+
log_warn "服务未在 10 秒内停止,正在强制停止..."
|
|
102
|
+
kill -9 "$pid" &>/dev/null || true
|
|
103
|
+
|
|
104
|
+
# 等待进程消失(最多 3 秒)
|
|
105
|
+
elapsed=0
|
|
106
|
+
while [ $elapsed -lt 3 ]; do
|
|
107
|
+
if [ -z "$(get_openclaw_pid)" ]; then
|
|
108
|
+
break
|
|
109
|
+
fi
|
|
110
|
+
sleep 1
|
|
111
|
+
elapsed=$((elapsed + 1))
|
|
112
|
+
done
|
|
113
|
+
|
|
114
|
+
# 验证:进程不存在或端口已关闭即为成功
|
|
115
|
+
# 使用端口检测作为辅助,避免僵尸进程导致误判
|
|
116
|
+
local current_pid
|
|
117
|
+
current_pid=$(get_openclaw_pid)
|
|
118
|
+
if [ -z "$current_pid" ]; then
|
|
119
|
+
log_info "OpenClaw 服务已强制停止。"
|
|
120
|
+
log_info "服务已成功停止。"
|
|
121
|
+
log_info "Success"
|
|
122
|
+
elif ! check_port "$PORT"; then
|
|
123
|
+
# 端口已关闭但进程可能还在(僵尸状态),也认为成功
|
|
124
|
+
log_info "OpenClaw 服务已停止(端口已关闭)。"
|
|
125
|
+
log_info "服务已成功停止。"
|
|
126
|
+
log_info "Success"
|
|
127
|
+
else
|
|
128
|
+
log_error "OpenClaw 服务停止失败!进程仍然存在且端口仍在监听。"
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# Systemd 模式:停止服务
|
|
134
|
+
stop_systemd() {
|
|
135
|
+
# 检查服务是否存在
|
|
32
136
|
if ! systemctl --user list-unit-files "$SERVICE_NAME" &>/dev/null; then
|
|
33
137
|
log_error "服务 $SERVICE_NAME 不存在。"
|
|
34
138
|
exit 1
|
|
35
139
|
fi
|
|
140
|
+
|
|
141
|
+
# 检查服务状态
|
|
142
|
+
log_info "检查 OpenClaw 服务状态..."
|
|
143
|
+
if systemctl --user is-active --quiet "$SERVICE_NAME"; then
|
|
144
|
+
log_info "OpenClaw 服务正在运行,准备停止..."
|
|
145
|
+
else
|
|
146
|
+
log_warn "OpenClaw 服务未在运行。"
|
|
147
|
+
exit 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
# 停止服务
|
|
151
|
+
log_info "正在停止 OpenClaw 服务..."
|
|
152
|
+
|
|
153
|
+
if systemctl --user stop "$SERVICE_NAME"; then
|
|
154
|
+
log_info "OpenClaw 服务停止成功!"
|
|
155
|
+
|
|
156
|
+
# 等待服务完全停止
|
|
157
|
+
sleep 2
|
|
158
|
+
|
|
159
|
+
# 验证服务状态
|
|
160
|
+
if systemctl --user is-active --quiet "$SERVICE_NAME"; then
|
|
161
|
+
log_warn "服务可能仍在运行,请检查进程。"
|
|
162
|
+
systemctl --user status "$SERVICE_NAME" --no-pager
|
|
163
|
+
else
|
|
164
|
+
log_info "服务已成功停止。"
|
|
165
|
+
log_info "Success"
|
|
166
|
+
fi
|
|
167
|
+
else
|
|
168
|
+
log_error "OpenClaw 服务停止失败!"
|
|
169
|
+
exit 1
|
|
170
|
+
fi
|
|
36
171
|
}
|
|
37
172
|
|
|
38
173
|
# 显示帮助信息
|
|
@@ -55,10 +190,10 @@ main() {
|
|
|
55
190
|
local force=""
|
|
56
191
|
|
|
57
192
|
# 解析命令行参数
|
|
58
|
-
while [
|
|
193
|
+
while [ $# -gt 0 ]; do
|
|
59
194
|
case $1 in
|
|
60
195
|
-f|--force)
|
|
61
|
-
force="
|
|
196
|
+
force="1"
|
|
62
197
|
shift
|
|
63
198
|
;;
|
|
64
199
|
-h|--help)
|
|
@@ -73,40 +208,12 @@ main() {
|
|
|
73
208
|
esac
|
|
74
209
|
done
|
|
75
210
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# 检查服务状态
|
|
80
|
-
log_info "检查 OpenClaw 服务状态..."
|
|
81
|
-
if systemctl --user is-active --quiet "$SERVICE_NAME"; then
|
|
82
|
-
log_info "OpenClaw 服务正在运行,准备停止..."
|
|
83
|
-
else
|
|
84
|
-
log_warn "OpenClaw 服务未在运行。"
|
|
85
|
-
exit 0
|
|
86
|
-
fi
|
|
87
|
-
|
|
88
|
-
# 停止服务
|
|
89
|
-
log_info "正在停止 OpenClaw 服务..."
|
|
90
|
-
|
|
91
|
-
if systemctl --user stop "$SERVICE_NAME"; then
|
|
92
|
-
log_info "OpenClaw 服务停止成功!"
|
|
93
|
-
|
|
94
|
-
# 等待服务完全停止
|
|
95
|
-
sleep 2
|
|
96
|
-
|
|
97
|
-
# 验证服务状态
|
|
98
|
-
if systemctl --user is-active --quiet "$SERVICE_NAME"; then
|
|
99
|
-
log_warn "服务可能仍在运行,请检查进程。"
|
|
100
|
-
systemctl --user status "$SERVICE_NAME" --no-pager
|
|
101
|
-
else
|
|
102
|
-
log_info "服务已成功停止。"
|
|
103
|
-
log_info "Success"
|
|
104
|
-
fi
|
|
211
|
+
if is_docker; then
|
|
212
|
+
stop_docker
|
|
105
213
|
else
|
|
106
|
-
|
|
107
|
-
exit 1
|
|
214
|
+
stop_systemd
|
|
108
215
|
fi
|
|
109
216
|
}
|
|
110
217
|
|
|
111
218
|
# 执行主函数
|
|
112
|
-
main "$@"
|
|
219
|
+
main "$@"
|
package/package.json
CHANGED
package/scripts/post-install.js
CHANGED
|
@@ -83,6 +83,21 @@ function installAndStartService() {
|
|
|
83
83
|
|
|
84
84
|
svc.on('install', () => {
|
|
85
85
|
console.log('[postinstall] 服务注册成功,正在启动...');
|
|
86
|
+
|
|
87
|
+
// 延迟 2 秒确保服务注册到 SCM,再设置开机自启 + 崩溃恢复
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
const cmdFailure = `sc.exe failure "${SERVICE_NAME}" reset= 0 actions= restart/0/restart/0/restart/0`;
|
|
90
|
+
exec(cmdFailure, (err) => {
|
|
91
|
+
if (err) console.error(`[postinstall] 设置恢复策略失败: ${err.message}`);
|
|
92
|
+
else console.log('[postinstall] 恢复策略已设置:崩溃后自动重启');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
exec(`sc.exe config "${SERVICE_NAME}" start= auto`, (err) => {
|
|
96
|
+
if (err) console.error(`[postinstall] 设置自动启动失败: ${err.message}`);
|
|
97
|
+
else console.log('[postinstall] 启动类型已设为:自动');
|
|
98
|
+
});
|
|
99
|
+
}, 2000);
|
|
100
|
+
|
|
86
101
|
svc.start();
|
|
87
102
|
});
|
|
88
103
|
|
|
Binary file
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<service>
|
|
2
|
+
<id>clawsubagentservice.exe</id>
|
|
3
|
+
<name>claw-subagent-service</name>
|
|
4
|
+
<description>OpenClaw Guard</description>
|
|
5
|
+
<executable>C:\Program Files\nodejs\node.exe</executable>
|
|
6
|
+
<argument>--harmony</argument>
|
|
7
|
+
<argument>--max_old_space_size=4096</argument>
|
|
8
|
+
<argument>D:\A-DM\dm-im\silent-service\node_modules\node-windows\lib\wrapper.js</argument>
|
|
9
|
+
<argument>--file</argument>
|
|
10
|
+
<argument>D:\A-DM\dm-im\silent-service\service\daemon.js</argument>
|
|
11
|
+
<argument>--scriptoptions=</argument>
|
|
12
|
+
<argument>--log</argument>
|
|
13
|
+
<argument>claw-subagent-service wrapper</argument>
|
|
14
|
+
<argument>--grow</argument>
|
|
15
|
+
<argument>0.25</argument>
|
|
16
|
+
<argument>--wait</argument>
|
|
17
|
+
<argument>1</argument>
|
|
18
|
+
<argument>--maxrestarts</argument>
|
|
19
|
+
<argument>3</argument>
|
|
20
|
+
<argument>--abortonerror</argument>
|
|
21
|
+
<argument>n</argument>
|
|
22
|
+
<argument>--stopparentfirst</argument>
|
|
23
|
+
<argument>undefined</argument>
|
|
24
|
+
<logmode>rotate</logmode>
|
|
25
|
+
<stoptimeout>30sec</stoptimeout>
|
|
26
|
+
<env name="undefined" value="undefined"/>
|
|
27
|
+
<serviceaccount>
|
|
28
|
+
<domain>XULIN</domain>
|
|
29
|
+
<user>LocalSystem</user>
|
|
30
|
+
<password></password>
|
|
31
|
+
</serviceaccount>
|
|
32
|
+
<workingdirectory>D:\A-DM\dm-im\silent-service</workingdirectory>
|
|
33
|
+
</service>
|
|
@@ -11,20 +11,33 @@ const SYSTEM_PROMPT = `你是 虾说app的 openclaw 运维助手智能体,职
|
|
|
11
11
|
3. **做备份**:改配置前自动备份到 config.json.bak.时间戳
|
|
12
12
|
|
|
13
13
|
## 常用运维指令
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
| \`openclaw doctor
|
|
18
|
-
| \`openclaw
|
|
19
|
-
| \`openclaw
|
|
20
|
-
| \`openclaw
|
|
21
|
-
| \`openclaw
|
|
22
|
-
| \`openclaw
|
|
23
|
-
| \`openclaw
|
|
24
|
-
| \`openclaw
|
|
14
|
+
|
|
15
|
+
| 命令 | 功能 | 容器内可用 |
|
|
16
|
+
|------|------|-----------|
|
|
17
|
+
| \`openclaw doctor\` | 诊断并自动修复问题 | ✅ |
|
|
18
|
+
| \`openclaw doctor --fix\` | 强制修复配置错误 | ✅ |
|
|
19
|
+
| \`openclaw status\` | 查看整体运行状态 | ✅ |
|
|
20
|
+
| \`openclaw models list\` | 查看可用模型 | ✅ |
|
|
21
|
+
| \`openclaw channels status\` | 查看通讯平台连接状态 | ✅ |
|
|
22
|
+
| \`openclaw logs --follow\` | 实时查看运行日志 | ✅ |
|
|
23
|
+
| \`openclaw gateway\` | **前台启动 Gateway**(端口 18789) | ✅ 推荐 |
|
|
24
|
+
| \`openclaw gateway --port 18789 --host 0.0.0.0 --verbose\` | 指定参数前台启动 | ✅ |
|
|
25
|
+
| \`openclaw onboard\` | 首次配置向导(交互式) | ✅ |
|
|
26
|
+
|
|
27
|
+
## Docker容器内不可用(需要 systemd)
|
|
28
|
+
|
|
29
|
+
| 命令 | 容器内替代方案 |
|
|
30
|
+
|------|---------|
|
|
31
|
+
| \`openclaw gateway start\` | 改用 \`openclaw gateway\` 前台运行 |
|
|
32
|
+
| \`openclaw gateway restart\` | 先 \`pkill -f "openclaw gateway"\` 再重新启动 |
|
|
33
|
+
| \`openclaw onboard --install-daemon\` | 改用 \`nohup openclaw gateway &\` 后台运行 |
|
|
34
|
+
| \`systemctl --user start openclaw-gateway.service\` | 容器无 systemd,用 \`nohup\` |
|
|
35
|
+
|
|
36
|
+
## 后台运行方案(容器内)nohup 后台运行
|
|
37
|
+
nohup openclaw gateway --port 18789 --host 0.0.0.0 --verbose > /var/log/openclaw.log 2>&1 &
|
|
25
38
|
|
|
26
39
|
## 修不好的时候,查资料顺序
|
|
27
|
-
1. \`openclaw
|
|
40
|
+
1. \`openclaw --help\` 先看本地帮助
|
|
28
41
|
2. https://docs.openclaw.ai 官方文档
|
|
29
42
|
3. https://github.com/openclaw/openclaw/issues GitHub 搜报错关键词
|
|
30
43
|
|
|
@@ -32,6 +45,8 @@ const SYSTEM_PROMPT = `你是 虾说app的 openclaw 运维助手智能体,职
|
|
|
32
45
|
- 改配置必须先备份
|
|
33
46
|
- 执行命令后汇报结果,别沉默
|
|
34
47
|
- 不知道就查资料,别瞎猜
|
|
48
|
+
- 看到 \`systemctl\` 相关报错,立即切换为 \`nohup\` 方案, 因为docker容器内是没有systemctl的。
|
|
49
|
+
- 超过6分钟 没有修复好就停下来,报告你遇到的问题,不要无限循环的进行修复。
|
|
35
50
|
- 不要对外透漏你是什么模型,不要说你是opencode,对外你就说你是 虾说运维大模型;
|
|
36
51
|
`;
|
|
37
52
|
|
|
@@ -93,12 +108,46 @@ async function getOrCreateGatewaySession(fallbackSessionId) {
|
|
|
93
108
|
throw new Error('无法获取或创建有效的 session ID');
|
|
94
109
|
}
|
|
95
110
|
|
|
111
|
+
async function ensureOpencodeRunning(log) {
|
|
112
|
+
try {
|
|
113
|
+
// 快速检查 4096 端口
|
|
114
|
+
await axios.get('http://127.0.0.1:4096/global/health', { timeout: 3000 });
|
|
115
|
+
return true;
|
|
116
|
+
} catch {
|
|
117
|
+
log('WARN', 'OpenCode 服务未运行,准备启动...');
|
|
118
|
+
|
|
119
|
+
const { exec } = require('child_process');
|
|
120
|
+
|
|
121
|
+
// 使用 nohup 启动,脱离终端
|
|
122
|
+
exec('nohup opencode serve --port 4096 --hostname 127.0.0.1 > /tmp/opencode.log 2>&1 &');
|
|
123
|
+
|
|
124
|
+
// 等待 8 秒让服务启动
|
|
125
|
+
await new Promise(resolve => setTimeout(resolve, 8000));
|
|
126
|
+
|
|
127
|
+
// 再次检查
|
|
128
|
+
try {
|
|
129
|
+
await axios.get('http://127.0.0.1:4096/global/health', { timeout: 3000 });
|
|
130
|
+
log('INFO', 'OpenCode 服务已启动');
|
|
131
|
+
return true;
|
|
132
|
+
} catch {
|
|
133
|
+
log('ERROR', 'OpenCode 服务启动失败');
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
96
139
|
async function forwardChatMessage(sessionId, content, onDelta, logFn, timeoutMs = 600000) {
|
|
97
140
|
const log = (level, message) => {
|
|
98
141
|
console.log(`[CHAT-DEBUG] ${level}: ${message}`);
|
|
99
142
|
if (logFn) logFn(level, message);
|
|
100
143
|
};
|
|
101
144
|
|
|
145
|
+
// 确保 opencode 在运行
|
|
146
|
+
const isRunning = await ensureOpencodeRunning(log);
|
|
147
|
+
if (!isRunning) {
|
|
148
|
+
throw new Error('OpenCode 服务无法启动,请检查环境');
|
|
149
|
+
}
|
|
150
|
+
|
|
102
151
|
log('DEBUG', `原始 sessionId: ${sessionId}`);
|
|
103
152
|
const realSessionId = await getOrCreateGatewaySession(sessionId);
|
|
104
153
|
log('DEBUG', `实际 sessionId: ${realSessionId}`);
|
|
@@ -117,77 +166,101 @@ async function forwardChatMessage(sessionId, content, onDelta, logFn, timeoutMs
|
|
|
117
166
|
|
|
118
167
|
log('DEBUG', `请求体包含 system 参数: ${!!requestBody.system}`);
|
|
119
168
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
169
|
+
try {
|
|
170
|
+
const response = await axios.post(url, requestBody, {
|
|
171
|
+
headers: { 'Content-Type': 'application/json' },
|
|
172
|
+
timeout: timeoutMs
|
|
173
|
+
});
|
|
124
174
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
175
|
+
const result = response.data || {};
|
|
176
|
+
log('DEBUG', `响应状态: ${response.status}`);
|
|
177
|
+
log('DEBUG', `响应数据 keys: ${Object.keys(result).join(', ')}`);
|
|
178
|
+
log('DEBUG', `响应数据: ${JSON.stringify(result).substring(0, 500)}`);
|
|
129
179
|
|
|
130
|
-
|
|
131
|
-
|
|
180
|
+
const parts = result.parts || [];
|
|
181
|
+
const info = result.info || {};
|
|
132
182
|
|
|
133
|
-
|
|
183
|
+
let fullContent = '';
|
|
134
184
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
185
|
+
for (const key of ['text', 'content', 'response', 'output', 'message', 'reply']) {
|
|
186
|
+
const val = result[key];
|
|
187
|
+
if (val && typeof val === 'string') {
|
|
188
|
+
fullContent = val;
|
|
189
|
+
log('DEBUG', `从顶层字段 ${key} 提取到内容`);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
141
192
|
}
|
|
142
|
-
}
|
|
143
193
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
194
|
+
if (!fullContent) {
|
|
195
|
+
for (const part of parts) {
|
|
196
|
+
const text = part.text || part.content || part.value || '';
|
|
197
|
+
const partType = part.type || '';
|
|
198
|
+
if (text && ['text', 'assistant', 'message', 'response', ''].includes(partType)) {
|
|
199
|
+
fullContent += String(text);
|
|
200
|
+
}
|
|
150
201
|
}
|
|
202
|
+
if (fullContent) log('DEBUG', `从 parts 中提取到内容, 长度: ${fullContent.length}`);
|
|
151
203
|
}
|
|
152
|
-
if (fullContent) log('DEBUG', `从 parts 中提取到内容, 长度: ${fullContent.length}`);
|
|
153
|
-
}
|
|
154
204
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
205
|
+
if (!fullContent) {
|
|
206
|
+
for (const part of parts) {
|
|
207
|
+
for (const key of ['text', 'content', 'value', 'output']) {
|
|
208
|
+
const val = part[key];
|
|
209
|
+
if (val && typeof val === 'string') {
|
|
210
|
+
fullContent += val;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
162
213
|
}
|
|
163
214
|
}
|
|
215
|
+
if (fullContent) log('DEBUG', `从所有 parts 兜底提取到内容, 长度: ${fullContent.length}`);
|
|
164
216
|
}
|
|
165
|
-
if (fullContent) log('DEBUG', `从所有 parts 兜底提取到内容, 长度: ${fullContent.length}`);
|
|
166
|
-
}
|
|
167
217
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
218
|
+
if (!fullContent) {
|
|
219
|
+
const infoText = info.text || info.content || '';
|
|
220
|
+
if (infoText) fullContent = infoText;
|
|
221
|
+
}
|
|
172
222
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
223
|
+
if (!fullContent) {
|
|
224
|
+
log('ERROR', `Gateway 返回空内容, parts 数量: ${parts.length}`);
|
|
225
|
+
throw new Error('Gateway 返回空内容');
|
|
226
|
+
}
|
|
177
227
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
228
|
+
log('DEBUG', `总内容长度: ${fullContent.length}, 开始模拟流式发送`);
|
|
229
|
+
const chunkSize = 50;
|
|
230
|
+
for (let i = 0; i < fullContent.length; i += chunkSize) {
|
|
231
|
+
const chunk = fullContent.slice(i, i + chunkSize);
|
|
232
|
+
await onDelta(chunk);
|
|
233
|
+
// 仅在非测试环境添加延迟
|
|
234
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
235
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
236
|
+
}
|
|
186
237
|
}
|
|
187
|
-
|
|
188
|
-
log('DEBUG', '流式发送完成');
|
|
238
|
+
log('DEBUG', '流式发送完成');
|
|
189
239
|
|
|
190
|
-
|
|
240
|
+
return fullContent;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
243
|
+
log('ERROR', `Gateway 请求异常: ${msg}`);
|
|
244
|
+
|
|
245
|
+
// 检测是否是超时错误(Gateway 卡死)
|
|
246
|
+
if (msg.includes('timeout') || msg.includes('ECONNABORTED') || msg.includes('ETIMEDOUT')) {
|
|
247
|
+
log('WARN', 'Gateway 超时,准备使用 nohup 重启...');
|
|
248
|
+
|
|
249
|
+
const { exec } = require('child_process');
|
|
250
|
+
|
|
251
|
+
// 1. 杀掉现有进程
|
|
252
|
+
exec('pkill -f "opencode serve"');
|
|
253
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
254
|
+
|
|
255
|
+
// 2. 使用 nohup 重新启动
|
|
256
|
+
exec('nohup opencode serve --port 4096 --hostname 127.0.0.1 > /tmp/opencode.log 2>&1 &');
|
|
257
|
+
await new Promise(resolve => setTimeout(resolve, 8000));
|
|
258
|
+
|
|
259
|
+
throw new Error('OpenCode 服务已重启,请稍后重试');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
191
264
|
}
|
|
192
265
|
|
|
193
266
|
module.exports = {
|
package/service/worker.js
CHANGED
|
@@ -15,6 +15,7 @@ const { startOpencodeService, stopOpencodeService } = require('./modules/opencod
|
|
|
15
15
|
|
|
16
16
|
const log = createLogger('worker');
|
|
17
17
|
const PORT = process.env.SILENT_SERVICE_PORT ? parseInt(process.env.SILENT_SERVICE_PORT, 10) : 28765;
|
|
18
|
+
const HOST = process.env.SILENT_SERVICE_HOST || '127.0.0.1';
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
// 捕获所有异常,强制打印到控制台(绕过 logger)
|
|
@@ -54,9 +55,20 @@ function findPidOnPort(port) {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
} else {
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
58
|
+
// 优先尝试 lsof,再兜底 ss / fuser / netstat(适配精简 Docker 镜像)
|
|
59
|
+
const commands = [
|
|
60
|
+
`lsof -i :${port} -t 2>/dev/null`,
|
|
61
|
+
`fuser ${port}/tcp 2>/dev/null`,
|
|
62
|
+
`ss -tlnp 2>/dev/null | grep ":${port}" | sed -n 's/.*pid=\\([0-9]*\\).*/\\1/p'`,
|
|
63
|
+
`netstat -tlnp 2>/dev/null | grep ":${port}" | sed -n 's/.*\\/\\([0-9]*\\).*/\\1/p'`,
|
|
64
|
+
];
|
|
65
|
+
for (const cmd of commands) {
|
|
66
|
+
try {
|
|
67
|
+
const out = execSync(cmd, { encoding: 'utf8', timeout: 5000 }).trim();
|
|
68
|
+
const pid = parseInt(out.split('\n')[0], 10);
|
|
69
|
+
if (!isNaN(pid) && pid > 0) return pid;
|
|
70
|
+
} catch { continue; }
|
|
71
|
+
}
|
|
60
72
|
}
|
|
61
73
|
} catch { /* port is free */ }
|
|
62
74
|
return null;
|
|
@@ -475,15 +487,15 @@ server.on('error', (err) => {
|
|
|
475
487
|
setTimeout(() => {
|
|
476
488
|
log.info(`[WORKER] 重新尝试监听端口 ${PORT}...`);
|
|
477
489
|
server.close(() => {});
|
|
478
|
-
server.listen(PORT,
|
|
490
|
+
server.listen(PORT, HOST);
|
|
479
491
|
}, 2000);
|
|
480
492
|
return;
|
|
481
493
|
}
|
|
482
494
|
log.error(`[WORKER] HTTP 服务错误: ${err.message}`);
|
|
483
495
|
});
|
|
484
496
|
|
|
485
|
-
server.listen(PORT,
|
|
486
|
-
log.info(`[WORKER] HTTP 服务已启动: http
|
|
497
|
+
server.listen(PORT, HOST, () => {
|
|
498
|
+
log.info(`[WORKER] HTTP 服务已启动: http://${HOST}:${PORT}/health`);
|
|
487
499
|
});
|
|
488
500
|
|
|
489
501
|
process.on('message', (msg) => {
|