@yeaft/webchat-agent 0.0.15 → 0.0.17

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/claude.js CHANGED
@@ -346,12 +346,13 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
346
346
  // stream-json 模式下 Claude 进程是持久运行的,for-await 在 result 后继续等待
347
347
  // 不清空 state.query 和 state.inputStream,下次用户消息直接通过同一个 inputStream 发送
348
348
  state.turnResultReceived = true;
349
- sendOutput(conversationId, message);
350
-
351
349
  resultHandled = true;
352
350
  state.turnActive = false;
353
351
 
354
- ctx.sendToServer({
352
+ // ★ await 确保 result 和 turn_completed 消息确实发送成功
353
+ // 不 await 会导致 encrypt 失败时消息静默丢失,前端卡在"思考中"
354
+ await sendOutput(conversationId, message);
355
+ await ctx.sendToServer({
355
356
  type: 'turn_completed',
356
357
  conversationId,
357
358
  claudeSessionId: state.claudeSessionId,
@@ -369,6 +370,9 @@ async function processClaudeOutput(conversationId, claudeQuery, state) {
369
370
  } catch (error) {
370
371
  if (error.name === 'AbortError') {
371
372
  console.log(`[SDK] Query aborted for ${conversationId}`);
373
+ } else if (resultHandled) {
374
+ // Turn 已正常完成,进程退出产生的 error 不发送给用户
375
+ console.warn(`[SDK] Ignoring post-result error for ${conversationId}: ${error.message}`);
372
376
  } else {
373
377
  console.error(`[SDK] Error for ${conversationId}:`, error.message);
374
378
  sendError(conversationId, error.message);
package/connection.js CHANGED
@@ -17,10 +17,28 @@ import {
17
17
  sendConversationList
18
18
  } from './conversation.js';
19
19
 
20
+ // 需要在断连期间缓冲的消息类型(Claude 输出相关的关键消息)
21
+ const BUFFERABLE_TYPES = new Set([
22
+ 'claude_output', 'turn_completed', 'conversation_closed',
23
+ 'session_id_update', 'compact_status', 'slash_commands_update',
24
+ 'background_task_started', 'background_task_output'
25
+ ]);
26
+
20
27
  // Send message to server (with encryption if available)
28
+ // 断连时对关键消息类型进行缓冲,重连后自动 flush
21
29
  async function sendToServer(msg) {
22
30
  if (!ctx.ws || ctx.ws.readyState !== WebSocket.OPEN) {
23
- console.warn(`[WS] Cannot send message, WebSocket not open (state: ${ctx.ws?.readyState})`);
31
+ // 缓冲关键消息
32
+ if (BUFFERABLE_TYPES.has(msg.type)) {
33
+ if (ctx.messageBuffer.length < ctx.messageBufferMaxSize) {
34
+ ctx.messageBuffer.push(msg);
35
+ console.log(`[WS] Buffered message: ${msg.type} (queue: ${ctx.messageBuffer.length})`);
36
+ } else {
37
+ console.warn(`[WS] Buffer full (${ctx.messageBufferMaxSize}), dropping: ${msg.type}`);
38
+ }
39
+ } else {
40
+ console.warn(`[WS] Cannot send message, WebSocket not open: ${msg.type}`);
41
+ }
24
42
  return;
25
43
  }
26
44
 
@@ -28,14 +46,31 @@ async function sendToServer(msg) {
28
46
  if (ctx.sessionKey) {
29
47
  const encrypted = await encrypt(msg, ctx.sessionKey);
30
48
  ctx.ws.send(JSON.stringify(encrypted));
31
- console.log(`[WS] Sent encrypted message: ${msg.type}`);
32
49
  } else {
33
50
  ctx.ws.send(JSON.stringify(msg));
34
- console.log(`[WS] Sent plain message: ${msg.type}`);
35
51
  }
36
52
  } catch (e) {
37
53
  console.error(`[WS] Error sending message ${msg.type}:`, e.message);
54
+ // 发送失败也缓冲
55
+ if (BUFFERABLE_TYPES.has(msg.type) && ctx.messageBuffer.length < ctx.messageBufferMaxSize) {
56
+ ctx.messageBuffer.push(msg);
57
+ console.log(`[WS] Send failed, buffered: ${msg.type}`);
58
+ }
59
+ }
60
+ }
61
+
62
+ // Flush 断连期间缓冲的消息
63
+ async function flushMessageBuffer() {
64
+ if (ctx.messageBuffer.length === 0) return;
65
+
66
+ const buffered = ctx.messageBuffer.splice(0);
67
+ console.log(`[WS] Flushing ${buffered.length} buffered messages...`);
68
+
69
+ for (const msg of buffered) {
70
+ await sendToServer(msg);
38
71
  }
72
+
73
+ console.log(`[WS] Flush complete`);
39
74
  }
40
75
 
41
76
  // Parse incoming message (decrypt if encrypted)
@@ -80,6 +115,9 @@ async function handleMessage(msg) {
80
115
 
81
116
  sendConversationList();
82
117
 
118
+ // ★ Flush 断连期间缓冲的消息
119
+ await flushMessageBuffer();
120
+
83
121
  // ★ Phase 1: 通知 server 同步完成
84
122
  sendToServer({ type: 'agent_sync_complete' });
85
123
  break;
@@ -273,7 +311,7 @@ async function handleMessage(msg) {
273
311
  const pkgName = ctx.pkgName || '@yeaft/webchat-agent';
274
312
  // Check latest version (async to avoid blocking heartbeat)
275
313
  const latestVersion = await new Promise((resolve, reject) => {
276
- execFile('npm', ['view', pkgName, 'version'], { stdio: 'pipe' }, (err, stdout) => {
314
+ execFile('npm', ['view', pkgName, 'version'], { stdio: 'pipe', shell: true }, (err, stdout) => {
277
315
  if (err) reject(err); else resolve(stdout.toString().trim());
278
316
  });
279
317
  });
@@ -285,7 +323,7 @@ async function handleMessage(msg) {
285
323
  console.log(`[Agent] Upgrading from ${ctx.agentVersion} to ${latestVersion}...`);
286
324
  // Use async execFile to avoid blocking event loop (heartbeat must keep running)
287
325
  await new Promise((resolve, reject) => {
288
- execFile('npm', ['install', '-g', `${pkgName}@latest`], { stdio: 'pipe' }, (err) => {
326
+ execFile('npm', ['install', '-g', `${pkgName}@latest`], { stdio: 'pipe', shell: true }, (err) => {
289
327
  if (err) reject(err); else resolve();
290
328
  });
291
329
  });
package/context.js CHANGED
@@ -19,6 +19,9 @@ export default {
19
19
  pendingAuthTempId: null,
20
20
  agentHeartbeatTimer: null,
21
21
  lastPongAt: 0,
22
+ // 断连期间的消息缓冲队列(重连后 flush)
23
+ messageBuffer: [],
24
+ messageBufferMaxSize: 5000, // 防止内存无限增长
22
25
  // 由 connection.js 注册的通信函数
23
26
  sendToServer: null,
24
27
  // 由 index.js 注册的配置保存函数
package/conversation.js CHANGED
@@ -55,7 +55,7 @@ export function sendConversationList() {
55
55
  }
56
56
 
57
57
  export function sendOutput(conversationId, data) {
58
- ctx.sendToServer({
58
+ return ctx.sendToServer({
59
59
  type: 'claude_output',
60
60
  conversationId,
61
61
  data
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",