@ynhcj/xiaoyi 2.2.2 → 2.2.4

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 +196 -60
  2. package/package.json +1 -1
package/dist/channel.js CHANGED
@@ -3,6 +3,57 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.xiaoyiPlugin = void 0;
4
4
  const runtime_1 = require("./runtime");
5
5
  const file_handler_1 = require("./file-handler");
6
+ // Import agent event functions - will be resolved at runtime
7
+ // Use dynamic require for compatibility
8
+ let onAgentEvent = null;
9
+ let registerAgentRunContext = null;
10
+ try {
11
+ // Try multiple import paths
12
+ let agentEvents = null;
13
+ // Try path 1: openclaw/dist/infra/agent-events
14
+ try {
15
+ agentEvents = require("openclaw/dist/infra/agent-events");
16
+ console.log("XiaoYi: [AGENT EVENT] Imported from openclaw/dist/infra/agent-events");
17
+ }
18
+ catch (e1) {
19
+ console.log("XiaoYi: [AGENT EVENT] Path 1 failed:", e1?.message || e1);
20
+ // Try path 2: ../openclaw/dist/infra/agent-events
21
+ try {
22
+ agentEvents = require("../openclaw/dist/infra/agent-events");
23
+ console.log("XiaoYi: [AGENT EVENT] Imported from ../openclaw/dist/infra/agent-events");
24
+ }
25
+ catch (e2) {
26
+ console.log("XiaoYi: [AGENT EVENT] Path 2 failed:", e2?.message || e2);
27
+ // Try path 3: ../../openclaw/dist/infra/agent-events
28
+ try {
29
+ agentEvents = require("../../openclaw/dist/infra/agent-events");
30
+ console.log("XiaoYi: [AGENT EVENT] Imported from ../../openclaw/dist/infra/agent-events");
31
+ }
32
+ catch (e3) {
33
+ console.error("XiaoYi: [AGENT EVENT] All import paths failed:");
34
+ console.error(" Path 1 (openclaw/dist/infra/agent-events):", e1?.message || e1);
35
+ console.error(" Path 2 (../openclaw/dist/infra/agent-events):", e2?.message || e2);
36
+ console.error(" Path 3 (../../openclaw/dist/infra/agent-events):", e3?.message || e3);
37
+ }
38
+ }
39
+ }
40
+ if (agentEvents) {
41
+ onAgentEvent = agentEvents.onAgentEvent;
42
+ registerAgentRunContext = agentEvents.registerAgentRunContext;
43
+ if (typeof onAgentEvent === "function") {
44
+ console.log("XiaoYi: [AGENT EVENT] ✓ onAgentEvent function imported successfully");
45
+ }
46
+ else {
47
+ console.error("XiaoYi: [AGENT EVENT] ✗ onAgentEvent is not a function, type:", typeof onAgentEvent);
48
+ }
49
+ }
50
+ else {
51
+ console.warn("XiaoYi: [AGENT EVENT] Could not import agent event module");
52
+ }
53
+ }
54
+ catch (error) {
55
+ console.error("XiaoYi: [AGENT EVENT] Fatal import error:", error?.message || error);
56
+ }
6
57
  /**
7
58
  * XiaoYi Channel Plugin
8
59
  * Implements OpenClaw ChannelPlugin interface for XiaoYi A2A protocol
@@ -307,15 +358,18 @@ exports.xiaoyiPlugin = {
307
358
  SenderId: senderId,
308
359
  OriginatingChannel: "xiaoyi",
309
360
  };
310
- // Use the correct API to dispatch the message (streaming mode enabled)
361
+ // Use the correct API to dispatch the message (streaming via onAgentEvent)
311
362
  try {
312
363
  const streamingEnabled = resolvedAccount.config.enableStreaming === true;
364
+ const hasAgentEventSupport = typeof onAgentEvent === "function";
313
365
  console.log("\n" + "=".repeat(60));
314
366
  console.log(`XiaoYi: [STREAMING] Starting message processing`);
315
367
  console.log(` Session: ${message.sessionId}`);
316
368
  console.log(` Task ID: ${message.params.id}`);
317
369
  console.log(` User input: ${bodyText.substring(0, 50)}${bodyText.length > 50 ? "..." : ""}`);
318
- console.log(` Streaming: ${streamingEnabled ? "ENABLED" : "DISABLED (enableStreaming=false)"}`);
370
+ console.log(` Config Streaming: ${streamingEnabled ? "ENABLED" : "DISABLED"}`);
371
+ console.log(` Agent Event Support: ${hasAgentEventSupport ? "AVAILABLE" : "NOT AVAILABLE"}`);
372
+ console.log(` Effective Streaming: ${streamingEnabled && hasAgentEventSupport ? "ENABLED (via onAgentEvent)" : "DISABLED"}`);
319
373
  console.log("=".repeat(60) + "\n");
320
374
  // Track response state for streaming
321
375
  let accumulatedText = "";
@@ -323,23 +377,145 @@ exports.xiaoyiPlugin = {
323
377
  let frameCount = 0;
324
378
  let partialCount = 0;
325
379
  const startTime = Date.now();
380
+ // Register agent event listener for streaming (NEW METHOD)
381
+ let unsubscribeAgentEvents;
382
+ let runId;
383
+ let isCompleted = false;
384
+ if (streamingEnabled && hasAgentEventSupport) {
385
+ // Subscribe to agent events BEFORE dispatching the message
386
+ unsubscribeAgentEvents = onAgentEvent((evt) => {
387
+ if (isCompleted)
388
+ return;
389
+ // Get runId from first event
390
+ if (!runId && evt.runId) {
391
+ runId = evt.runId;
392
+ console.log(`XiaoYi: [AGENT EVENT] Registered runId: ${runId}`);
393
+ }
394
+ // Only process events for this run
395
+ if (runId && evt.runId !== runId)
396
+ return;
397
+ const elapsed = Date.now() - startTime;
398
+ // Handle streaming text events
399
+ if (evt.stream === "assistant" && typeof evt.data?.text === "string") {
400
+ const newText = evt.data.text;
401
+ if (!newText)
402
+ return;
403
+ // Calculate delta text
404
+ const previousLength = accumulatedText.length;
405
+ accumulatedText = newText;
406
+ const deltaText = newText.slice(previousLength);
407
+ console.log(`\n>>> XiaoYi: [AGENT EVENT] Stream frame #${++partialCount} at ${elapsed}ms`);
408
+ console.log(` Run ID: ${evt.runId}`);
409
+ console.log(` Delta length: ${deltaText.length} chars`);
410
+ console.log(` Total length: ${newText.length} chars`);
411
+ console.log(` Delta content: "${deltaText}"`);
412
+ // Send PARTIAL frame with delta content
413
+ const partialResponse = {
414
+ sessionId: message.sessionId,
415
+ messageId: `partial_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
416
+ timestamp: Date.now(),
417
+ agentId: resolvedAccount.config.agentId,
418
+ sender: {
419
+ id: resolvedAccount.config.agentId,
420
+ name: "OpenClaw Agent",
421
+ type: "agent",
422
+ },
423
+ content: {
424
+ type: "text",
425
+ text: deltaText || newText,
426
+ },
427
+ status: "processing",
428
+ };
429
+ const conn = runtime.getConnection();
430
+ if (conn) {
431
+ conn.sendResponse(partialResponse, taskId, message.sessionId, false, true)
432
+ .then(() => {
433
+ console.log(` ✓ SENT (isFinal=false, append=true)\n`);
434
+ })
435
+ .catch((err) => {
436
+ console.error(` ✗ Send failed: ${err}\n`);
437
+ });
438
+ }
439
+ }
440
+ // Handle lifecycle events (completion/error)
441
+ if (evt.stream === "lifecycle") {
442
+ const phase = evt.data?.phase;
443
+ if (phase === "end") {
444
+ isCompleted = true;
445
+ console.log("\n" + "-".repeat(60));
446
+ console.log(`XiaoYi: [AGENT EVENT] Lifecycle phase: end`);
447
+ console.log(` Run ID: ${evt.runId}`);
448
+ console.log(` Elapsed: ${elapsed}ms`);
449
+ console.log(` Total frames: ${partialCount}`);
450
+ console.log(` Final length: ${accumulatedText.length} chars`);
451
+ console.log("-".repeat(60) + "\n");
452
+ // Send FINAL frame
453
+ const response = {
454
+ sessionId: message.sessionId,
455
+ messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
456
+ timestamp: Date.now(),
457
+ agentId: resolvedAccount.config.agentId,
458
+ sender: {
459
+ id: resolvedAccount.config.agentId,
460
+ name: "OpenClaw Agent",
461
+ type: "agent",
462
+ },
463
+ content: {
464
+ type: "text",
465
+ text: accumulatedText,
466
+ },
467
+ status: "success",
468
+ };
469
+ const conn = runtime.getConnection();
470
+ if (conn) {
471
+ conn.sendResponse(response, taskId, message.sessionId, true, false)
472
+ .then(() => {
473
+ console.log(`✓ XiaoYi: FINAL frame sent (isFinal=true, append=false)\n`);
474
+ })
475
+ .catch((err) => {
476
+ console.error(`✗ Final send failed: ${err}\n`);
477
+ });
478
+ }
479
+ // Unsubscribe from events
480
+ if (unsubscribeAgentEvents) {
481
+ unsubscribeAgentEvents();
482
+ unsubscribeAgentEvents = undefined;
483
+ }
484
+ }
485
+ else if (phase === "error") {
486
+ isCompleted = true;
487
+ console.error(`XiaoYi: [AGENT EVENT] Error phase:`, evt.data?.error);
488
+ // Unsubscribe from events
489
+ if (unsubscribeAgentEvents) {
490
+ unsubscribeAgentEvents();
491
+ unsubscribeAgentEvents = undefined;
492
+ }
493
+ }
494
+ }
495
+ });
496
+ console.log(`XiaoYi: [AGENT EVENT] Listener registered for streaming`);
497
+ }
498
+ else if (streamingEnabled && !hasAgentEventSupport) {
499
+ console.warn(`XiaoYi: [WARN] Streaming enabled but onAgentEvent not available, falling back to non-streaming mode`);
500
+ }
326
501
  await pluginRuntime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
327
502
  ctx: msgContext,
328
503
  cfg: config,
329
504
  dispatcherOptions: {
330
505
  deliver: async (payload) => {
506
+ // NOTE: onAgentEvent does NOT emit "assistant" stream events in production
507
+ // Only lifecycle and tool events are emitted
508
+ // So we must handle the final result here
331
509
  const elapsed = Date.now() - startTime;
332
510
  const completeText = payload.text || "";
333
511
  accumulatedText = completeText;
334
512
  console.log("\n" + "-".repeat(60));
335
- console.log(`XiaoYi: [STREAMING] DELIVER callback triggered`);
513
+ console.log(`XiaoYi: [DELIVER] Final result received`);
336
514
  console.log(` Elapsed: ${elapsed}ms`);
337
- console.log(` Frame: #${++frameCount}`);
338
- console.log(` Total length: ${completeText.length} chars`);
339
- console.log(` Partial frames sent: ${partialCount}`);
340
- console.log(` Content preview: ${completeText.substring(0, 100)}...`);
515
+ console.log(` Length: ${completeText.length} chars`);
516
+ console.log(` Streaming frames sent: ${partialCount}`);
341
517
  console.log("-".repeat(60) + "\n");
342
- // Send FINAL frame with complete content
518
+ // Send FINAL frame
343
519
  const response = {
344
520
  sessionId: message.sessionId,
345
521
  messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
@@ -352,74 +528,34 @@ exports.xiaoyiPlugin = {
352
528
  },
353
529
  content: {
354
530
  type: "text",
355
- text: accumulatedText, // Complete content
531
+ text: accumulatedText,
356
532
  },
357
533
  status: "success",
358
534
  };
359
535
  const conn = runtime.getConnection();
360
536
  if (conn) {
361
- // Use append=false for final frame (complete content)
362
537
  await conn.sendResponse(response, taskId, message.sessionId, true, false);
363
- console.log(`✓ XiaoYi: FINAL frame sent (isFinal=true, append=false, length=${accumulatedText.length})\n`);
538
+ console.log(`✓ XiaoYi: FINAL frame sent (isFinal=true, append=false)\n`);
364
539
  }
365
540
  },
366
541
  onIdle: async () => {
367
542
  const elapsed = Date.now() - startTime;
368
543
  console.log("\n" + "=".repeat(60));
369
- console.log(`XiaoYi: [STREAMING] Processing complete`);
544
+ console.log(`XiaoYi: [IDLE] Processing complete`);
370
545
  console.log(` Total time: ${elapsed}ms`);
371
- console.log(` Total frames: ${frameCount}`);
372
- console.log(` Partial frames: ${partialCount}`);
546
+ console.log(` Streaming frames: ${partialCount}`);
373
547
  console.log(` Final length: ${accumulatedText.length} chars`);
374
548
  console.log("=".repeat(60) + "\n");
375
- },
376
- },
377
- replyOptions: streamingEnabled ? {
378
- // Enable streaming responses through onPartialReply callback
379
- disableBlockStreaming: false, // CRITICAL: Enable block streaming
380
- onPartialReply: async (payload) => {
381
- const elapsed = Date.now() - startTime;
382
- const newText = payload.text || "";
383
- if (!newText) {
384
- console.log(`XiaoYi: [STREAMING] Skipping empty partial response at ${elapsed}ms`);
385
- return;
386
- }
387
- // Calculate delta text (增量部分)
388
- const previousLength = accumulatedText.length;
389
- accumulatedText = newText;
390
- const deltaText = newText.slice(previousLength);
391
- console.log(`\n>>> XiaoYi: [PARTIAL] Frame #${++partialCount} at ${elapsed}ms`);
392
- console.log(` Previous length: ${previousLength}`);
393
- console.log(` New length: ${newText.length}`);
394
- console.log(` Delta length: ${deltaText.length}`);
395
- console.log(` Delta content: "${deltaText}"`);
396
- console.log(` Accumulated so far: "${newText}"`);
397
- // Send PARTIAL frame with delta content
398
- const partialResponse = {
399
- sessionId: message.sessionId,
400
- messageId: `partial_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
401
- timestamp: Date.now(),
402
- agentId: resolvedAccount.config.agentId,
403
- sender: {
404
- id: resolvedAccount.config.agentId,
405
- name: "OpenClaw Agent",
406
- type: "agent",
407
- },
408
- content: {
409
- type: "text",
410
- text: deltaText || newText, // Send delta only (or full if delta is empty)
411
- },
412
- status: "processing", // Mark as processing
413
- };
414
- const conn = runtime.getConnection();
415
- if (conn) {
416
- // Use append=true for streaming frames (服务端会追加)
417
- await conn.sendResponse(partialResponse, taskId, message.sessionId, false, true);
418
- console.log(` ✓ SENT (isFinal=false, append=true)\n`);
549
+ // Cleanup event listener if still active
550
+ if (unsubscribeAgentEvents && !isCompleted) {
551
+ console.log(`XiaoYi: [IDLE] Cleaning up event listener`);
552
+ unsubscribeAgentEvents();
553
+ unsubscribeAgentEvents = undefined;
419
554
  }
420
555
  },
421
- } : undefined, // No replyOptions when streaming is disabled
422
- images: images.length > 0 ? images : undefined, // Pass images to AI processing
556
+ },
557
+ replyOptions: undefined, // onPartialReply is not used
558
+ images: images.length > 0 ? images : undefined,
423
559
  });
424
560
  }
425
561
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
4
4
  "description": "XiaoYi channel plugin for OpenClaw",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",