claw-subagent-service 0.0.61 → 0.0.63
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
|
@@ -97,9 +97,16 @@ class MessageHandler {
|
|
|
97
97
|
} else {
|
|
98
98
|
// 如果配置了代理地址,使用流式处理
|
|
99
99
|
if (this.isStreamingEnabled) {
|
|
100
|
-
|
|
101
|
-
this.
|
|
102
|
-
})
|
|
100
|
+
try {
|
|
101
|
+
await this.handleNormalMessageStream(msg);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
this.log?.error(`[MessageHandler] 流式处理失败,回退到非流式: ${err.message}`);
|
|
104
|
+
const reply = await this.handleNormalMessage(msg);
|
|
105
|
+
if (reply) {
|
|
106
|
+
const targetId = this.getReplyTarget(msg);
|
|
107
|
+
await this.sendFn(targetId, reply, msg.conversationType);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
103
110
|
} else {
|
|
104
111
|
// 降级到非流式处理
|
|
105
112
|
const reply = await this.handleNormalMessage(msg);
|
|
@@ -164,9 +171,22 @@ class MessageHandler {
|
|
|
164
171
|
|
|
165
172
|
// 如果配置了代理地址,使用流式处理
|
|
166
173
|
if (this.isStreamingEnabled) {
|
|
167
|
-
|
|
168
|
-
this.
|
|
169
|
-
})
|
|
174
|
+
try {
|
|
175
|
+
await this.handleNormalMessageStream(msg);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
this.log?.error(`[MessageHandler] 流式处理失败,回退到 CLI: ${err.message}`);
|
|
178
|
+
// 回退到非流式 CLI 调用
|
|
179
|
+
try {
|
|
180
|
+
const reply = await this.openclawClient.chat(msg.content, msg.senderUserId);
|
|
181
|
+
if (reply) {
|
|
182
|
+
this.log?.info(`[MessageHandler] AI 回复: ${reply.substring(0, 50)}...`);
|
|
183
|
+
await this.sendFn(targetId, reply, msg.conversationType);
|
|
184
|
+
}
|
|
185
|
+
} catch (cliErr) {
|
|
186
|
+
this.log?.error(`[MessageHandler] CLI 回退也失败: ${cliErr.message}`);
|
|
187
|
+
await this.sendFn(targetId, `❌ 处理失败: ${cliErr.message}`, msg.conversationType);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
170
190
|
return;
|
|
171
191
|
}
|
|
172
192
|
|
|
@@ -199,36 +219,48 @@ class MessageHandler {
|
|
|
199
219
|
this.log?.info(`[MessageHandler] 开始流式处理,streamId=${streamId}`);
|
|
200
220
|
|
|
201
221
|
// 2. 调用 OpenClaw SSE
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
let hasSentChunk = false;
|
|
223
|
+
try {
|
|
224
|
+
await this.openclawClient.chatStream(
|
|
225
|
+
msg.content,
|
|
226
|
+
msg.senderUserId,
|
|
227
|
+
async (delta) => {
|
|
228
|
+
buffer += delta;
|
|
229
|
+
// 策略:每 30 个字符或遇到句末标点发送一次片段
|
|
230
|
+
if (buffer.length >= 30 || /[。!?.!?\n]$/.test(delta)) {
|
|
231
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, buffer, streamId, isFirstChunk, false);
|
|
232
|
+
isFirstChunk = false;
|
|
233
|
+
hasSentChunk = true;
|
|
234
|
+
buffer = '';
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
async (fullText) => {
|
|
238
|
+
// 发送剩余缓冲区和结束标记
|
|
239
|
+
if (buffer.trim()) {
|
|
240
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, buffer, streamId, isFirstChunk, true);
|
|
241
|
+
hasSentChunk = true;
|
|
242
|
+
} else if (!isFirstChunk) {
|
|
243
|
+
// 已经发送过内容,单独发送结束标记
|
|
244
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, '', streamId, false, true);
|
|
245
|
+
hasSentChunk = true;
|
|
246
|
+
} else {
|
|
247
|
+
// 完全没有收到内容,发送错误提示
|
|
248
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, '抱歉,AI 暂时没有回复内容。', streamId, true, true);
|
|
249
|
+
hasSentChunk = true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!hasSentChunk) {
|
|
253
|
+
throw new Error('流式消息发送失败,没有任何片段成功送达');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.log?.info(`[MessageHandler] 流式消息完成,streamId=${streamId}, 总长度: ${fullText.length}`);
|
|
224
257
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
);
|
|
258
|
+
);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
this.log?.error(`[MessageHandler] 流式处理错误: ${err.message}`);
|
|
261
|
+
await this._sendStreamChunk(fromUserId, targetId, conversationType, '抱歉,AI 响应出现错误,请稍后重试。', streamId, isFirstChunk, true);
|
|
262
|
+
throw err;
|
|
263
|
+
}
|
|
232
264
|
}
|
|
233
265
|
|
|
234
266
|
/**
|
|
@@ -248,7 +280,9 @@ class MessageHandler {
|
|
|
248
280
|
);
|
|
249
281
|
this.log?.info(`[MessageHandler] typing 状态已发送: ${fromUserId} -> ${targetId}`);
|
|
250
282
|
} catch (err) {
|
|
251
|
-
|
|
283
|
+
const url = `${this.config.apiBaseUrl}/im/api/proxy/stream/typing`;
|
|
284
|
+
const status = err.response?.status;
|
|
285
|
+
this.log?.warn(`[MessageHandler] 发送 typing 状态失败: ${err.message}, url=${url}, status=${status || 'N/A'}`);
|
|
252
286
|
}
|
|
253
287
|
}
|
|
254
288
|
|
|
@@ -272,7 +306,9 @@ class MessageHandler {
|
|
|
272
306
|
{ timeout: 10000 }
|
|
273
307
|
);
|
|
274
308
|
} catch (err) {
|
|
275
|
-
|
|
309
|
+
const url = `${this.config.apiBaseUrl}/im/api/proxy/stream/publish`;
|
|
310
|
+
const status = err.response?.status;
|
|
311
|
+
this.log?.warn(`[MessageHandler] 发送流式消息失败: ${err.message}, url=${url}, status=${status || 'N/A'}`);
|
|
276
312
|
}
|
|
277
313
|
}
|
|
278
314
|
|
|
@@ -332,9 +332,16 @@ class OpenClawClient {
|
|
|
332
332
|
continue;
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
335
|
+
if (is404 && isLast) {
|
|
336
|
+
this.log?.error(`[OpenClawClient] 所有 SSE 端点均返回 404。OpenClaw chatCompletions 端点未启用。`);
|
|
337
|
+
this.log?.error(`[OpenClawClient] 请检查 ~/.openclaw/openclaw.json 中是否包含:`);
|
|
338
|
+
this.log?.error(`[OpenClawClient] gateway.http.endpoints.chatCompletions.enabled = true`);
|
|
339
|
+
this.log?.error(`[OpenClawClient] 修改后请重启 OpenClaw gateway: openclaw gateway`);
|
|
340
|
+
} else {
|
|
341
|
+
this.log?.error(`[OpenClawClient] SSE 请求失败: ${err.message}`);
|
|
342
|
+
}
|
|
343
|
+
// 不再内部调用 onError,让错误通过 Promise reject 向上传播,便于调用方回退
|
|
344
|
+
throw err;
|
|
338
345
|
}
|
|
339
346
|
}
|
|
340
347
|
}
|
|
@@ -367,7 +374,7 @@ class OpenClawClient {
|
|
|
367
374
|
});
|
|
368
375
|
|
|
369
376
|
return new Promise((resolve, reject) => {
|
|
370
|
-
response.data.on('data', (chunk) => {
|
|
377
|
+
response.data.on('data', async (chunk) => {
|
|
371
378
|
buffer += chunk.toString();
|
|
372
379
|
const lines = buffer.split('\n');
|
|
373
380
|
buffer = lines.pop(); // 保留未完整的最后一行
|
|
@@ -384,7 +391,12 @@ class OpenClawClient {
|
|
|
384
391
|
const delta = data.choices?.[0]?.delta?.content;
|
|
385
392
|
if (delta) {
|
|
386
393
|
fullText += delta;
|
|
387
|
-
|
|
394
|
+
try {
|
|
395
|
+
await onDelta?.(delta);
|
|
396
|
+
} catch (err) {
|
|
397
|
+
reject(err);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
388
400
|
}
|
|
389
401
|
} catch {
|
|
390
402
|
// 忽略无法解析的 JSON 行
|
|
@@ -392,10 +404,14 @@ class OpenClawClient {
|
|
|
392
404
|
}
|
|
393
405
|
});
|
|
394
406
|
|
|
395
|
-
response.data.on('end', () => {
|
|
407
|
+
response.data.on('end', async () => {
|
|
396
408
|
this.log?.info(`[OpenClawClient] SSE 流结束,总长度: ${fullText.length}`);
|
|
397
|
-
|
|
398
|
-
|
|
409
|
+
try {
|
|
410
|
+
await onDone?.(fullText);
|
|
411
|
+
resolve();
|
|
412
|
+
} catch (err) {
|
|
413
|
+
reject(err);
|
|
414
|
+
}
|
|
399
415
|
});
|
|
400
416
|
|
|
401
417
|
response.data.on('error', (err) => {
|