@ynhcj/xiaoyi 2.4.3 → 2.4.5

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/dist/channel.js CHANGED
@@ -322,6 +322,7 @@ exports.xiaoyiPlugin = {
322
322
  const taskId = runtime.getTaskIdForSession(sessionId) || `task_${Date.now()}`;
323
323
  const startTime = Date.now();
324
324
  let accumulatedText = "";
325
+ let sentTextLength = 0; // Track sent text length for streaming
325
326
  // ==================== CREATE ABORT CONTROLLER ====================
326
327
  // Create AbortController for this session to allow cancelation
327
328
  const { controller: abortController, signal: abortSignal } = runtime.createAbortControllerForSession(sessionId);
@@ -341,36 +342,20 @@ exports.xiaoyiPlugin = {
341
342
  console.log("=".repeat(60) + "\n");
342
343
  const conn = runtime.getConnection();
343
344
  if (conn) {
344
- const timeoutResponse = {
345
- sessionId: sessionId,
346
- messageId: `timeout_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
347
- timestamp: Date.now(),
348
- agentId: resolvedAccount.config.agentId,
349
- sender: {
350
- id: resolvedAccount.config.agentId,
351
- name: "OpenClaw Agent",
352
- type: "agent",
353
- },
354
- content: {
355
- type: "text",
356
- text: timeoutConfig.message,
357
- },
358
- status: "success",
359
- };
360
345
  try {
361
- // Send timeout message with isFinal=false (not final), append=false (replace)
362
- await conn.sendResponse(timeoutResponse, taskId, sessionId, false, false);
363
- console.log(`[TIMEOUT] Timeout message sent successfully to session ${sessionId}\n`);
346
+ // Send status update instead of artifact-update to keep conversation active
347
+ await conn.sendStatusUpdate(taskId, sessionId, timeoutConfig.message);
348
+ console.log(`[TIMEOUT] Status update sent successfully to session ${sessionId}\n`);
364
349
  // Restart the timeout timer - allow repeated timeout messages
365
350
  console.log(`[TIMEOUT] Restarting timeout timer for session ${sessionId}...\n`);
366
351
  runtime.setTimeoutForSession(sessionId, createTimeoutHandler());
367
352
  }
368
353
  catch (error) {
369
- console.error(`[TIMEOUT] Failed to send timeout message:`, error);
354
+ console.error(`[TIMEOUT] Failed to send status update:`, error);
370
355
  }
371
356
  }
372
357
  else {
373
- console.error(`[TIMEOUT] Connection not available, cannot send timeout message\n`);
358
+ console.error(`[TIMEOUT] Connection not available, cannot send status update\n`);
374
359
  }
375
360
  };
376
361
  };
@@ -381,15 +366,17 @@ exports.xiaoyiPlugin = {
381
366
  ctx: msgContext,
382
367
  cfg: config,
383
368
  dispatcherOptions: {
384
- deliver: async (payload) => {
369
+ deliver: async (payload, info) => {
385
370
  const elapsed = Date.now() - startTime;
386
371
  const completeText = payload.text || "";
387
372
  accumulatedText = completeText;
373
+ const kind = info.kind; // "tool" | "block" | "final"
388
374
  // Check if session was aborted
389
375
  if (runtime.isSessionAborted(sessionId)) {
390
376
  console.log("\n" + "=".repeat(60));
391
377
  console.log(`[ABORT] Response received AFTER abort`);
392
378
  console.log(` Session: ${sessionId}`);
379
+ console.log(` Kind: ${kind}`);
393
380
  console.log(` Elapsed: ${elapsed}ms`);
394
381
  console.log(` Action: DISCARDING (session was canceled)`);
395
382
  console.log("=".repeat(60) + "\n");
@@ -401,6 +388,7 @@ exports.xiaoyiPlugin = {
401
388
  console.log("\n" + "=".repeat(60));
402
389
  console.log(`[TIMEOUT] Response received AFTER timeout`);
403
390
  console.log(` Session: ${sessionId}`);
391
+ console.log(` Kind: ${kind}`);
404
392
  console.log(` Elapsed: ${elapsed}ms`);
405
393
  console.log(` Action: DISCARDING (timeout message already sent)`);
406
394
  console.log("=".repeat(60) + "\n");
@@ -410,47 +398,105 @@ exports.xiaoyiPlugin = {
410
398
  // If response is empty, don't clear timeout (let it trigger)
411
399
  if (!completeText || completeText.length === 0) {
412
400
  console.log("\n" + "=".repeat(60));
413
- console.log(`[TIMEOUT] Empty response detected`);
401
+ console.log(`[STREAM] Empty ${kind} response detected`);
414
402
  console.log(` Session: ${sessionId}`);
415
403
  console.log(` Elapsed: ${elapsed}ms`);
416
- console.log(` Action: KEEPING TIMEOUT (session conflict detected)`);
404
+ console.log(` Action: SKIPPING (empty response)`);
417
405
  console.log("=".repeat(60) + "\n");
418
406
  // Don't send anything, and don't clear timeout
419
407
  return;
420
408
  }
421
- console.log("\n" + "-".repeat(60));
422
- console.log(`XiaoYi: [DELIVER] AI response received`);
423
- console.log(` Elapsed: ${elapsed}ms`);
424
- console.log(` Length: ${completeText.length} chars`);
425
- console.log(` Preview: ${completeText.substring(0, 100)}...`);
426
- console.log("-".repeat(60) + "\n");
427
- // Send final response to XiaoYi
428
- const response = {
429
- sessionId: sessionId,
430
- messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
431
- timestamp: Date.now(),
432
- agentId: resolvedAccount.config.agentId,
433
- sender: {
434
- id: resolvedAccount.config.agentId,
435
- name: "OpenClaw Agent",
436
- type: "agent",
437
- },
438
- content: {
439
- type: "text",
440
- text: accumulatedText,
441
- },
442
- status: "success",
443
- };
444
409
  const conn = runtime.getConnection();
445
- if (conn) {
410
+ if (!conn) {
411
+ console.error(`✗ XiaoYi: Connection not available\n`);
412
+ return;
413
+ }
414
+ // ==================== STREAMING LOGIC ====================
415
+ // For "block" kind: send incremental text (streaming)
416
+ // For "final" kind: send complete text (final message)
417
+ if (kind === "block") {
418
+ // Calculate incremental text
419
+ const incrementalText = completeText.slice(sentTextLength);
420
+ if (incrementalText.length > 0) {
421
+ console.log("\n" + "-".repeat(60));
422
+ console.log(`XiaoYi: [STREAM] Incremental block received`);
423
+ console.log(` Session: ${sessionId}`);
424
+ console.log(` Elapsed: ${elapsed}ms`);
425
+ console.log(` Total length: ${completeText.length} chars`);
426
+ console.log(` Incremental: +${incrementalText.length} chars (sent: ${sentTextLength})`);
427
+ console.log(` Preview: "${incrementalText.substring(0, 50)}${incrementalText.length > 50 ? "..." : ""}"`);
428
+ console.log("-".repeat(60) + "\n");
429
+ const response = {
430
+ sessionId: sessionId,
431
+ messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
432
+ timestamp: Date.now(),
433
+ agentId: resolvedAccount.config.agentId,
434
+ sender: {
435
+ id: resolvedAccount.config.agentId,
436
+ name: "OpenClaw Agent",
437
+ type: "agent",
438
+ },
439
+ content: {
440
+ type: "text",
441
+ text: incrementalText,
442
+ },
443
+ status: "success",
444
+ };
445
+ // Send incremental text, NOT final, append mode
446
+ await conn.sendResponse(response, taskId, sessionId, false, true);
447
+ console.log(`✓ XiaoYi: Stream chunk sent (+${incrementalText.length} chars)\n`);
448
+ // Update sent length
449
+ sentTextLength = completeText.length;
450
+ }
451
+ else {
452
+ console.log("\n" + "-".repeat(60));
453
+ console.log(`XiaoYi: [STREAM] No new text to send`);
454
+ console.log(` Session: ${sessionId}`);
455
+ console.log(` Total length: ${completeText.length} chars`);
456
+ console.log(` Already sent: ${sentTextLength} chars`);
457
+ console.log("-".repeat(60) + "\n");
458
+ }
459
+ }
460
+ else if (kind === "final") {
461
+ console.log("\n" + "=".repeat(60));
462
+ console.log(`XiaoYi: [STREAM] Final response received`);
463
+ console.log(` Session: ${sessionId}`);
464
+ console.log(` Elapsed: ${elapsed}ms`);
465
+ console.log(` Total length: ${completeText.length} chars`);
466
+ console.log(` Preview: "${completeText.substring(0, 100)}..."`);
467
+ console.log("=".repeat(60) + "\n");
468
+ const response = {
469
+ sessionId: sessionId,
470
+ messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
471
+ timestamp: Date.now(),
472
+ agentId: resolvedAccount.config.agentId,
473
+ sender: {
474
+ id: resolvedAccount.config.agentId,
475
+ name: "OpenClaw Agent",
476
+ type: "agent",
477
+ },
478
+ content: {
479
+ type: "text",
480
+ text: completeText,
481
+ },
482
+ status: "success",
483
+ };
484
+ // Send complete text as FINAL, NOT append
446
485
  await conn.sendResponse(response, taskId, sessionId, true, false);
447
- console.log(`✓ XiaoYi: Response sent successfully\n`);
486
+ console.log(`✓ XiaoYi: Final response sent (${completeText.length} chars)\n`);
487
+ // Clear timeout and abort controller on final
488
+ runtime.markSessionCompleted(sessionId);
489
+ // Note: Don't clear abort controller here, let onIdle handle it
490
+ // This ensures we can still abort if there's any pending cleanup
448
491
  }
449
- else {
450
- console.error(`✗ XiaoYi: Connection not available\n`);
492
+ else if (kind === "tool") {
493
+ // Tool results - typically not sent as messages
494
+ console.log("\n" + "-".repeat(60));
495
+ console.log(`XiaoYi: [STREAM] Tool result received (not forwarded)`);
496
+ console.log(` Session: ${sessionId}`);
497
+ console.log(` Elapsed: ${elapsed}ms`);
498
+ console.log("-".repeat(60) + "\n");
451
499
  }
452
- // Clear timeout as response was sent successfully
453
- runtime.markSessionCompleted(sessionId);
454
500
  },
455
501
  onIdle: async () => {
456
502
  const elapsed = Date.now() - startTime;
@@ -70,6 +70,11 @@ export declare class XiaoYiWebSocketManager extends EventEmitter {
70
70
  * Send clear context response to specific server
71
71
  */
72
72
  sendClearContextResponse(requestId: string, sessionId: string, success?: boolean, targetServer?: ServerId): Promise<void>;
73
+ /**
74
+ * Send status update (for intermediate status messages, e.g., timeout warnings)
75
+ * This uses "status-update" event type which keeps the conversation active
76
+ */
77
+ sendStatusUpdate(taskId: string, sessionId: string, message: string, targetServer?: ServerId): Promise<void>;
73
78
  /**
74
79
  * Send tasks cancel response to specific server
75
80
  */
package/dist/websocket.js CHANGED
@@ -456,6 +456,64 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
456
456
  throw error;
457
457
  }
458
458
  }
459
+ /**
460
+ * Send status update (for intermediate status messages, e.g., timeout warnings)
461
+ * This uses "status-update" event type which keeps the conversation active
462
+ */
463
+ async sendStatusUpdate(taskId, sessionId, message, targetServer) {
464
+ const serverId = targetServer || this.sessionServerMap.get(sessionId);
465
+ if (!serverId) {
466
+ console.error(`[STATUS] Unknown server for session ${sessionId}`);
467
+ throw new Error(`Cannot send status update: unknown session ${sessionId}`);
468
+ }
469
+ const ws = serverId === 'server1' ? this.ws1 : this.ws2;
470
+ if (!ws || ws.readyState !== ws_1.default.OPEN) {
471
+ console.error(`[STATUS] ${serverId} not connected`);
472
+ throw new Error(`${serverId} is not available`);
473
+ }
474
+ // Create unique ID for this status update
475
+ const messageId = `status_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
476
+ const jsonRpcResponse = {
477
+ jsonrpc: "2.0",
478
+ id: messageId,
479
+ result: {
480
+ taskId: taskId,
481
+ kind: "status-update",
482
+ final: false, // IMPORTANT: Not final, keeps conversation active
483
+ status: {
484
+ message: {
485
+ role: "agent",
486
+ parts: [
487
+ {
488
+ kind: "text",
489
+ text: message,
490
+ },
491
+ ],
492
+ },
493
+ state: "working", // Indicates task is still being processed
494
+ },
495
+ },
496
+ };
497
+ const outboundMessage = {
498
+ msgType: "agent_response",
499
+ agentId: this.config.agentId,
500
+ sessionId: sessionId,
501
+ taskId: taskId,
502
+ msgDetail: JSON.stringify(jsonRpcResponse),
503
+ };
504
+ console.log(`[STATUS] Sending status update to ${serverId}:`);
505
+ console.log(` sessionId: ${sessionId}`);
506
+ console.log(` taskId: ${taskId}`);
507
+ console.log(` message: ${message}`);
508
+ console.log(` final: false, state: working\n`);
509
+ try {
510
+ ws.send(JSON.stringify(outboundMessage));
511
+ }
512
+ catch (error) {
513
+ console.error(`[STATUS] Failed to send to ${serverId}:`, error);
514
+ throw error;
515
+ }
516
+ }
459
517
  /**
460
518
  * Send tasks cancel response to specific server
461
519
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi",
3
- "version": "2.4.3",
3
+ "version": "2.4.5",
4
4
  "description": "XiaoYi channel plugin for OpenClaw",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -29,6 +29,11 @@
29
29
  "blurb": "小艺 A2A 协议支持,通过 WebSocket 连接。",
30
30
  "order": 80,
31
31
  "aliases": ["xy"]
32
+ },
33
+ "install": {
34
+ "npmSpec": "@ynhcj/xiaoyi",
35
+ "localPath": ".",
36
+ "defaultChoice": "npm"
32
37
  }
33
38
  },
34
39
  "peerDependencies": {