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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claw-subagent-service",
3
- "version": "0.0.61",
3
+ "version": "0.0.63",
4
4
  "description": "虾说智能助手",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -97,9 +97,16 @@ class MessageHandler {
97
97
  } else {
98
98
  // 如果配置了代理地址,使用流式处理
99
99
  if (this.isStreamingEnabled) {
100
- this.handleNormalMessageStream(msg).catch(err => {
101
- this.log?.error(`[MessageHandler] 流式处理异常: ${err.message}`);
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
- this.handleNormalMessageStream(msg).catch(err => {
168
- this.log?.error(`[MessageHandler] 流式处理异常: ${err.message}`);
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
- return this.openclawClient.chatStream(
203
- msg.content,
204
- msg.senderUserId,
205
- async (delta) => {
206
- buffer += delta;
207
- // 策略:每 30 个字符或遇到句末标点发送一次片段
208
- if (buffer.length >= 30 || /[。!?.!?\n]$/.test(delta)) {
209
- await this._sendStreamChunk(fromUserId, targetId, conversationType, buffer, streamId, isFirstChunk, false);
210
- isFirstChunk = false;
211
- buffer = '';
212
- }
213
- },
214
- async (fullText) => {
215
- // 发送剩余缓冲区和结束标记
216
- if (buffer.trim()) {
217
- await this._sendStreamChunk(fromUserId, targetId, conversationType, buffer, streamId, isFirstChunk, true);
218
- } else if (!isFirstChunk) {
219
- // 已经发送过内容,单独发送结束标记
220
- await this._sendStreamChunk(fromUserId, targetId, conversationType, '', streamId, false, true);
221
- } else {
222
- // 完全没有收到内容,发送错误提示
223
- await this._sendStreamChunk(fromUserId, targetId, conversationType, '抱歉,AI 暂时没有回复内容。', streamId, true, true);
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
- this.log?.info(`[MessageHandler] 流式消息完成,streamId=${streamId}, 总长度: ${fullText.length}`);
226
- },
227
- async (err) => {
228
- this.log?.error(`[MessageHandler] 流式处理错误: ${err.message}`);
229
- await this._sendStreamChunk(fromUserId, targetId, conversationType, '抱歉,AI 响应出现错误,请稍后重试。', streamId, isFirstChunk, true);
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
- this.log?.warn(`[MessageHandler] 发送 typing 状态失败: ${err.message}`);
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
- this.log?.warn(`[MessageHandler] 发送流式消息失败: ${err.message}`);
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
- this.log?.error(`[OpenClawClient] SSE 请求失败: ${err.message}`);
336
- onError?.(err);
337
- return;
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
- onDelta?.(delta);
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
- onDone?.(fullText);
398
- resolve();
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) => {
package/service/worker.js CHANGED
@@ -232,6 +232,8 @@ function loadRongCloudConfig() {
232
232
  config.heartbeatInterval = 20;
233
233
  }
234
234
 
235
+ log.info(`[WORKER] 最终 apiBaseUrl: ${config.apiBaseUrl}`);
236
+
235
237
  if (config.token && config.accountId) {
236
238
  return config;
237
239
  }