claw-subagent-service 0.0.63 → 0.0.65
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
CHANGED
|
@@ -13,11 +13,11 @@ class HeartbeatManager {
|
|
|
13
13
|
|
|
14
14
|
start(getMacAddress, getOpenClawStatus) {
|
|
15
15
|
const interval = (this.config.heartbeatInterval || 20) * 1000;
|
|
16
|
-
this.log?.info(`[HeartbeatManager] 启动心跳定时器,间隔: ${interval}ms`);
|
|
17
|
-
|
|
16
|
+
// this.log?.info(`[HeartbeatManager] 启动心跳定时器,间隔: ${interval}ms`);
|
|
17
|
+
|
|
18
18
|
this.timer = setInterval(async () => {
|
|
19
19
|
if (!this.rongcloudClient?.isConnected) return;
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
try {
|
|
22
22
|
const mac = getMacAddress();
|
|
23
23
|
const status = await getOpenClawStatus(this.config.openclawPort || 18789);
|
|
@@ -31,7 +31,7 @@ class HeartbeatManager {
|
|
|
31
31
|
}
|
|
32
32
|
);
|
|
33
33
|
if (sent) {
|
|
34
|
-
this.log?.info('[HeartbeatManager] 心跳已发送');
|
|
34
|
+
// this.log?.info('[HeartbeatManager] 心跳已发送');
|
|
35
35
|
} else {
|
|
36
36
|
this.log?.warn('[HeartbeatManager] 心跳发送失败');
|
|
37
37
|
}
|
|
@@ -62,15 +62,15 @@ class DashboardReporter {
|
|
|
62
62
|
start(getMacAddress) {
|
|
63
63
|
const interval = 30000; // 30秒
|
|
64
64
|
this.log?.info(`[DashboardReporter] 启动仪表盘上报定时器,间隔: ${interval}ms`);
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
this.timer = setInterval(async () => {
|
|
67
67
|
if (!this.rongcloudClient?.isConnected) return;
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
try {
|
|
70
70
|
const data = await collectDashboardData();
|
|
71
71
|
const timestamp = data.diagnostics?.generatedAt || new Date().toISOString();
|
|
72
72
|
const mac = getMacAddress();
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
// 拆分为 6 条消息发送
|
|
75
75
|
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_SESSIONS, {
|
|
76
76
|
mac_address: mac,
|
|
@@ -78,21 +78,21 @@ class DashboardReporter {
|
|
|
78
78
|
sessionStatuses: data.sessionStatuses,
|
|
79
79
|
timestamp,
|
|
80
80
|
}, 1000);
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_JOBS, {
|
|
83
83
|
mac_address: mac,
|
|
84
84
|
cronJobs: data.cronJobs,
|
|
85
85
|
approvals: data.approvals,
|
|
86
86
|
timestamp,
|
|
87
87
|
}, 1000);
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_PROJECTS, {
|
|
90
90
|
mac_address: mac,
|
|
91
91
|
projects: data.projects,
|
|
92
92
|
tasks: data.tasks,
|
|
93
93
|
timestamp,
|
|
94
94
|
}, 1000);
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_SUMMARIES, {
|
|
97
97
|
mac_address: mac,
|
|
98
98
|
projectSummaries: data.projectSummaries,
|
|
@@ -101,20 +101,20 @@ class DashboardReporter {
|
|
|
101
101
|
diagnostics: data.diagnostics,
|
|
102
102
|
timestamp,
|
|
103
103
|
}, 1000);
|
|
104
|
-
|
|
104
|
+
|
|
105
105
|
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_SESSIONS_CONTEXTS, {
|
|
106
106
|
mac_address: mac,
|
|
107
107
|
sessionsContexts: (data.sessionsContexts || []).slice(0, 50),
|
|
108
108
|
timestamp,
|
|
109
109
|
}, 1000);
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
await this.sendChunk(RongyunMessageTypeEnum.DASHBOARD_USAGE_EVENTS, {
|
|
112
112
|
mac_address: mac,
|
|
113
113
|
usageEvents: (data.usageEvents || []).slice(-100),
|
|
114
114
|
timestamp,
|
|
115
115
|
}, 0);
|
|
116
|
-
|
|
117
|
-
this.log?.info('[DashboardReporter] 所有数据块发送完成');
|
|
116
|
+
|
|
117
|
+
// this.log?.info('[DashboardReporter] 所有数据块发送完成');
|
|
118
118
|
} catch (err) {
|
|
119
119
|
this.log?.error(`[DashboardReporter] 上报异常: ${err.message}`);
|
|
120
120
|
}
|
|
@@ -132,7 +132,7 @@ class DashboardReporter {
|
|
|
132
132
|
} catch (err) {
|
|
133
133
|
this.log?.error(`[DashboardReporter] ${msgType} 发送异常: ${err.message}`);
|
|
134
134
|
}
|
|
135
|
-
|
|
135
|
+
|
|
136
136
|
if (delayMs > 0) {
|
|
137
137
|
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
138
138
|
}
|
|
@@ -22,7 +22,7 @@ class RongyunMessageSender {
|
|
|
22
22
|
buildMessage(msgType, content, requestId) {
|
|
23
23
|
const mac = getMacAddress();
|
|
24
24
|
const secret = generateSecret(mac, this.config.secretKey || 'secret_key');
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
return {
|
|
27
27
|
msg_type: msgType,
|
|
28
28
|
source_im_id: this.config.accountId || '',
|
|
@@ -46,8 +46,8 @@ class RongyunMessageSender {
|
|
|
46
46
|
|
|
47
47
|
try {
|
|
48
48
|
const messagePayload = this.buildMessage(msgType, content, requestId);
|
|
49
|
-
|
|
50
|
-
this.log?.info(`[RongyunMessageSender] 发送协议消息 -> ${this.serverImId}, type=${msgType}`);
|
|
49
|
+
|
|
50
|
+
// this.log?.info(`[RongyunMessageSender] 发送协议消息 -> ${this.serverImId}, type=${msgType}`);
|
|
51
51
|
|
|
52
52
|
const result = await this.rongcloudClient.sendMessage(
|
|
53
53
|
this.serverImId,
|
|
@@ -56,7 +56,7 @@ class RongyunMessageSender {
|
|
|
56
56
|
);
|
|
57
57
|
|
|
58
58
|
if (result) {
|
|
59
|
-
this.log?.info(`[RongyunMessageSender] ${msgType} 发送成功`);
|
|
59
|
+
// this.log?.info(`[RongyunMessageSender] ${msgType} 发送成功`);
|
|
60
60
|
} else {
|
|
61
61
|
this.log?.warn(`[RongyunMessageSender] ${msgType} 发送失败`);
|
|
62
62
|
}
|
|
@@ -208,7 +208,7 @@ class MessageHandler {
|
|
|
208
208
|
async handleNormalMessageStream(msg) {
|
|
209
209
|
const targetId = this.getReplyTarget(msg);
|
|
210
210
|
const streamId = `${Date.now()}-${Math.random().toString(36).substring(2, 10)}`;
|
|
211
|
-
let
|
|
211
|
+
let seq = 0;
|
|
212
212
|
let buffer = '';
|
|
213
213
|
const fromUserId = this.config.accountId;
|
|
214
214
|
const conversationType = msg.conversationType;
|
|
@@ -226,26 +226,24 @@ class MessageHandler {
|
|
|
226
226
|
msg.senderUserId,
|
|
227
227
|
async (delta) => {
|
|
228
228
|
buffer += delta;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
hasSentChunk = true;
|
|
234
|
-
buffer = '';
|
|
235
|
-
}
|
|
229
|
+
seq += 1;
|
|
230
|
+
this.log?.info(`[MessageHandler] onDelta: seq=${seq}, delta_len=${delta.length}, buffer_len=${buffer.length}`);
|
|
231
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, buffer, streamId, seq === 1, false, seq);
|
|
232
|
+
hasSentChunk = true;
|
|
236
233
|
},
|
|
237
234
|
async (fullText) => {
|
|
238
|
-
|
|
235
|
+
this.log?.info(`[MessageHandler] onDone 触发, fullText.length=${fullText.length}, buffer.length=${buffer.length}, hasSentChunk=${hasSentChunk}`);
|
|
239
236
|
if (buffer.trim()) {
|
|
240
|
-
|
|
237
|
+
seq += 1;
|
|
238
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, buffer, streamId, seq === 1, true, seq);
|
|
241
239
|
hasSentChunk = true;
|
|
242
|
-
} else if (
|
|
240
|
+
} else if (hasSentChunk) {
|
|
243
241
|
// 已经发送过内容,单独发送结束标记
|
|
244
|
-
|
|
245
|
-
|
|
242
|
+
seq += 1;
|
|
243
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, buffer, streamId, false, true, seq);
|
|
246
244
|
} else {
|
|
247
245
|
// 完全没有收到内容,发送错误提示
|
|
248
|
-
await this._sendStreamChunk(fromUserId, targetId, conversationType, '抱歉,AI 暂时没有回复内容。', streamId, true, true);
|
|
246
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, '抱歉,AI 暂时没有回复内容。', streamId, true, true, 1);
|
|
249
247
|
hasSentChunk = true;
|
|
250
248
|
}
|
|
251
249
|
|
|
@@ -258,7 +256,7 @@ class MessageHandler {
|
|
|
258
256
|
);
|
|
259
257
|
} catch (err) {
|
|
260
258
|
this.log?.error(`[MessageHandler] 流式处理错误: ${err.message}`);
|
|
261
|
-
await this._sendStreamChunk(fromUserId, targetId, conversationType, '抱歉,AI 响应出现错误,请稍后重试。', streamId,
|
|
259
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, '抱歉,AI 响应出现错误,请稍后重试。', streamId, true, true, 1);
|
|
262
260
|
throw err;
|
|
263
261
|
}
|
|
264
262
|
}
|
|
@@ -289,10 +287,14 @@ class MessageHandler {
|
|
|
289
287
|
/**
|
|
290
288
|
* 发送流式消息片段(通过 Python 后端代理)
|
|
291
289
|
*/
|
|
292
|
-
async _sendStreamChunk(fromUserId, targetId, conversationType, content, streamId, isFirstChunk, isLastChunk) {
|
|
293
|
-
|
|
290
|
+
async _sendStreamChunk(fromUserId, targetId, conversationType, content, streamId, isFirstChunk, isLastChunk, seq = 1) {
|
|
291
|
+
this.log?.info(`[MessageHandler] _sendStreamChunk ENTRY: target=${targetId}, streamId=${streamId}, seq=${seq}, first=${isFirstChunk}, last=${isLastChunk}, content_len=${content?.length || 0}`);
|
|
292
|
+
if (!this.isStreamingEnabled) {
|
|
293
|
+
this.log?.warn('[MessageHandler] _sendStreamChunk skipped: isStreamingEnabled=false');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
294
296
|
try {
|
|
295
|
-
await axios.post(
|
|
297
|
+
const resp = await axios.post(
|
|
296
298
|
`${this.config.apiBaseUrl}/im/api/proxy/stream/publish`,
|
|
297
299
|
{
|
|
298
300
|
fromUserId,
|
|
@@ -301,14 +303,16 @@ class MessageHandler {
|
|
|
301
303
|
streamId,
|
|
302
304
|
isFirstChunk,
|
|
303
305
|
isLastChunk,
|
|
304
|
-
conversationType
|
|
306
|
+
conversationType,
|
|
307
|
+
seq
|
|
305
308
|
},
|
|
306
309
|
{ timeout: 10000 }
|
|
307
310
|
);
|
|
311
|
+
this.log?.info(`[MessageHandler] _sendStreamChunk 成功: status=${resp.status}, seq=${seq}`);
|
|
308
312
|
} catch (err) {
|
|
309
313
|
const url = `${this.config.apiBaseUrl}/im/api/proxy/stream/publish`;
|
|
310
314
|
const status = err.response?.status;
|
|
311
|
-
this.log?.warn(`[MessageHandler] 发送流式消息失败: ${err.message}, url=${url}, status=${status || 'N/A'}`);
|
|
315
|
+
this.log?.warn(`[MessageHandler] 发送流式消息失败: ${err.message}, url=${url}, status=${status || 'N/A'}, seq=${seq}`);
|
|
312
316
|
}
|
|
313
317
|
}
|
|
314
318
|
|
|
@@ -153,11 +153,60 @@ function checkPort(port) {
|
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* 确保 openclaw.json 中启用了 chatCompletions 端点
|
|
158
|
+
* 在启动 gateway 前调用,避免 SSE 流式调用返回 404
|
|
159
|
+
*/
|
|
160
|
+
function ensureChatCompletionsConfig(log) {
|
|
161
|
+
const realHome = getRealHomeDir();
|
|
162
|
+
const openclawDir = path.join(realHome, '.openclaw');
|
|
163
|
+
const configPath = path.join(openclawDir, 'openclaw.json');
|
|
164
|
+
|
|
165
|
+
let settings = {};
|
|
166
|
+
let existed = false;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
if (fs.existsSync(configPath)) {
|
|
170
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
171
|
+
settings = JSON.parse(content);
|
|
172
|
+
existed = true;
|
|
173
|
+
}
|
|
174
|
+
} catch (err) {
|
|
175
|
+
log?.warn(`[OpenClawClient] 读取 openclaw.json 失败: ${err.message}`);
|
|
176
|
+
settings = {};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const current = settings.gateway?.http?.endpoints?.chatCompletions?.enabled;
|
|
180
|
+
if (current === true) {
|
|
181
|
+
log?.info('[OpenClawClient] openclaw.json 中 chatCompletions 已启用');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!settings.gateway) settings.gateway = {};
|
|
186
|
+
if (!settings.gateway.http) settings.gateway.http = {};
|
|
187
|
+
if (!settings.gateway.http.endpoints) settings.gateway.http.endpoints = {};
|
|
188
|
+
if (!settings.gateway.http.endpoints.chatCompletions) settings.gateway.http.endpoints.chatCompletions = {};
|
|
189
|
+
settings.gateway.http.endpoints.chatCompletions.enabled = true;
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
if (!fs.existsSync(openclawDir)) {
|
|
193
|
+
fs.mkdirSync(openclawDir, { recursive: true });
|
|
194
|
+
}
|
|
195
|
+
fs.writeFileSync(configPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
196
|
+
log?.info(`[OpenClawClient] 已自动在 openclaw.json 中启用 chatCompletions (${existed ? '更新' : '新建'})`);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
log?.error(`[OpenClawClient] 写入 openclaw.json 失败: ${err.message}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
156
202
|
/**
|
|
157
203
|
* 启动 OpenClaw gateway
|
|
158
204
|
*/
|
|
159
205
|
function startOpenClawGateway(log) {
|
|
160
206
|
return new Promise((resolve) => {
|
|
207
|
+
// 启动前自动修复配置
|
|
208
|
+
ensureChatCompletionsConfig(log);
|
|
209
|
+
|
|
161
210
|
log?.info('[OpenClawClient] 正在启动 OpenClaw gateway...');
|
|
162
211
|
|
|
163
212
|
const child = spawn('openclaw', ['gateway'], {
|
|
@@ -230,8 +279,7 @@ class OpenClawClient {
|
|
|
230
279
|
* 确保 OpenClaw gateway 在运行
|
|
231
280
|
*/
|
|
232
281
|
async ensureGatewayRunning() {
|
|
233
|
-
|
|
234
|
-
|
|
282
|
+
// 每次调用都实际检查端口,避免 gateway 崩溃后缓存状态失效
|
|
235
283
|
const gatewayRunning = await checkPort(18789);
|
|
236
284
|
|
|
237
285
|
if (gatewayRunning) {
|
|
@@ -240,6 +288,8 @@ class OpenClawClient {
|
|
|
240
288
|
return true;
|
|
241
289
|
}
|
|
242
290
|
|
|
291
|
+
this.gatewayStarted = false;
|
|
292
|
+
|
|
243
293
|
// 避免并发启动
|
|
244
294
|
if (this.gatewayStarting) {
|
|
245
295
|
this.log?.info('[OpenClawClient] gateway 正在启动中,等待...');
|