@ynhcj/xiaoyi 2.4.6 → 2.4.8

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.
Files changed (2) hide show
  1. package/dist/channel.js +52 -11
  2. package/package.json +6 -6
package/dist/channel.js CHANGED
@@ -323,6 +323,7 @@ exports.xiaoyiPlugin = {
323
323
  const startTime = Date.now();
324
324
  let accumulatedText = "";
325
325
  let sentTextLength = 0; // Track sent text length for streaming
326
+ let hasSentFinal = false; // Track if final content has been sent (to prevent duplicate isFinal=true)
326
327
  // ==================== CREATE ABORT CONTROLLER ====================
327
328
  // Create AbortController for this session to allow cancelation
328
329
  const { controller: abortController, signal: abortSignal } = runtime.createAbortControllerForSession(sessionId);
@@ -479,8 +480,16 @@ exports.xiaoyiPlugin = {
479
480
  console.log(` Session: ${sessionId}`);
480
481
  console.log(` Elapsed: ${elapsed}ms`);
481
482
  console.log(` Total length: ${completeText.length} chars`);
483
+ console.log(` Has already sent final: ${hasSentFinal}`);
482
484
  console.log(` Preview: "${completeText.substring(0, 100)}..."`);
483
485
  console.log("=".repeat(60) + "\n");
486
+ // Check if we've already sent final content - prevent duplicate isFinal=true
487
+ if (hasSentFinal) {
488
+ console.log(`XiaoYi: [STREAM] Skipping duplicate final response (already sent)\n`);
489
+ // Don't send anything, but clear timeout to prevent timeout warnings
490
+ runtime.clearSessionTimeout(sessionId);
491
+ return;
492
+ }
484
493
  const response = {
485
494
  sessionId: sessionId,
486
495
  messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
@@ -497,13 +506,16 @@ exports.xiaoyiPlugin = {
497
506
  },
498
507
  status: "success",
499
508
  };
500
- // Send complete text as FINAL, NOT append
501
- await conn.sendResponse(response, taskId, sessionId, true, false);
502
- console.log(`✓ XiaoYi: Final response sent (${completeText.length} chars)\n`);
503
- // Clear timeout and abort controller on final
504
- runtime.markSessionCompleted(sessionId);
505
- // Note: Don't clear abort controller here, let onIdle handle it
506
- // This ensures we can still abort if there's any pending cleanup
509
+ // Send complete text but NOT as final yet - use append mode to keep stream alive
510
+ // The true isFinal=true will be sent in onIdle callback
511
+ await conn.sendResponse(response, taskId, sessionId, false, true);
512
+ console.log(`✓ XiaoYi: Final response content sent (${completeText.length} chars, stream kept alive)\n`);
513
+ // Mark that we've sent the final content
514
+ hasSentFinal = true;
515
+ // Clear timeout to prevent timeout warnings, but don't mark session as completed yet
516
+ runtime.clearSessionTimeout(sessionId);
517
+ // Note: We DON'T call markSessionCompleted here
518
+ // The session will be marked completed in onIdle when we send isFinal=true
507
519
  }
508
520
  else if (kind === "tool") {
509
521
  // Tool results - typically not sent as messages
@@ -520,15 +532,44 @@ exports.xiaoyiPlugin = {
520
532
  console.log(`XiaoYi: [IDLE] Processing complete`);
521
533
  console.log(` Total time: ${elapsed}ms`);
522
534
  console.log(` Final length: ${accumulatedText.length} chars`);
523
- // Only clear timeout if we have a valid response
524
- // If empty, keep timeout running to handle session conflict
535
+ console.log(` Has sent final: ${hasSentFinal}`);
536
+ // Only send final close message if we have valid content
525
537
  if (accumulatedText.length > 0) {
538
+ const conn = runtime.getConnection();
539
+ if (conn) {
540
+ try {
541
+ // Send the true final response with isFinal=true to properly close the stream
542
+ const finalResponse = {
543
+ sessionId: sessionId,
544
+ messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
545
+ timestamp: Date.now(),
546
+ agentId: resolvedAccount.config.agentId,
547
+ sender: {
548
+ id: resolvedAccount.config.agentId,
549
+ name: "OpenClaw Agent",
550
+ type: "agent",
551
+ },
552
+ content: {
553
+ type: "text",
554
+ text: accumulatedText,
555
+ },
556
+ status: "success",
557
+ };
558
+ // Send with isFinal=true to properly close the A2A communication stream
559
+ await conn.sendResponse(finalResponse, taskId, sessionId, true, false);
560
+ console.log(`✓ XiaoYi: True isFinal=true sent to close stream\n`);
561
+ }
562
+ catch (error) {
563
+ console.error(`✗ XiaoYi: Failed to send final close message:`, error);
564
+ }
565
+ }
566
+ // Now mark session as completed and clean up
526
567
  runtime.markSessionCompleted(sessionId);
527
568
  runtime.clearAbortControllerForSession(sessionId);
528
- console.log(`[TIMEOUT] Timeout cleared (valid response)\n`);
569
+ console.log(`[TIMEOUT] Session completed and cleaned up\n`);
529
570
  }
530
571
  else {
531
- console.log(`[TIMEOUT] Keeping timeout (no valid response)\n`);
572
+ console.log(`[TIMEOUT] No valid response, keeping timeout active\n`);
532
573
  }
533
574
  console.log("=".repeat(60) + "\n");
534
575
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi",
3
- "version": "2.4.6",
3
+ "version": "2.4.8",
4
4
  "description": "XiaoYi channel plugin for OpenClaw",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,6 +20,11 @@
20
20
  "extensions": ["xiaoyi.js"],
21
21
  "channels": ["xiaoyi"],
22
22
  "installDependencies": true,
23
+ "install": {
24
+ "npmSpec": "@ynhcj/xiaoyi",
25
+ "localPath": ".",
26
+ "defaultChoice": "npm"
27
+ },
23
28
  "channel": {
24
29
  "id": "xiaoyi",
25
30
  "label": "XiaoYi",
@@ -29,11 +34,6 @@
29
34
  "blurb": "小艺 A2A 协议支持,通过 WebSocket 连接。",
30
35
  "order": 80,
31
36
  "aliases": ["xy"]
32
- },
33
- "install": {
34
- "npmSpec": "@ynhcj/xiaoyi",
35
- "localPath": ".",
36
- "defaultChoice": "npm"
37
37
  }
38
38
  },
39
39
  "peerDependencies": {