@ynhcj/xiaoyi 2.2.2 → 2.2.3

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 +162 -60
  2. package/package.json +1 -1
package/dist/channel.js CHANGED
@@ -3,6 +3,21 @@ 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 to import from the built openclaw package
12
+ const agentEvents = require("openclaw/dist/infra/agent-events");
13
+ onAgentEvent = agentEvents.onAgentEvent;
14
+ registerAgentRunContext = agentEvents.registerAgentRunContext;
15
+ console.log("XiaoYi: [AGENT EVENT] Successfully imported agent event functions");
16
+ }
17
+ catch (error) {
18
+ console.warn("XiaoYi: [AGENT EVENT] Could not import agent event functions:", error);
19
+ console.warn("XiaoYi: [AGENT EVENT] Streaming will be disabled");
20
+ }
6
21
  /**
7
22
  * XiaoYi Channel Plugin
8
23
  * Implements OpenClaw ChannelPlugin interface for XiaoYi A2A protocol
@@ -307,15 +322,18 @@ exports.xiaoyiPlugin = {
307
322
  SenderId: senderId,
308
323
  OriginatingChannel: "xiaoyi",
309
324
  };
310
- // Use the correct API to dispatch the message (streaming mode enabled)
325
+ // Use the correct API to dispatch the message (streaming via onAgentEvent)
311
326
  try {
312
327
  const streamingEnabled = resolvedAccount.config.enableStreaming === true;
328
+ const hasAgentEventSupport = typeof onAgentEvent === "function";
313
329
  console.log("\n" + "=".repeat(60));
314
330
  console.log(`XiaoYi: [STREAMING] Starting message processing`);
315
331
  console.log(` Session: ${message.sessionId}`);
316
332
  console.log(` Task ID: ${message.params.id}`);
317
333
  console.log(` User input: ${bodyText.substring(0, 50)}${bodyText.length > 50 ? "..." : ""}`);
318
- console.log(` Streaming: ${streamingEnabled ? "ENABLED" : "DISABLED (enableStreaming=false)"}`);
334
+ console.log(` Config Streaming: ${streamingEnabled ? "ENABLED" : "DISABLED"}`);
335
+ console.log(` Agent Event Support: ${hasAgentEventSupport ? "AVAILABLE" : "NOT AVAILABLE"}`);
336
+ console.log(` Effective Streaming: ${streamingEnabled && hasAgentEventSupport ? "ENABLED (via onAgentEvent)" : "DISABLED"}`);
319
337
  console.log("=".repeat(60) + "\n");
320
338
  // Track response state for streaming
321
339
  let accumulatedText = "";
@@ -323,23 +341,147 @@ exports.xiaoyiPlugin = {
323
341
  let frameCount = 0;
324
342
  let partialCount = 0;
325
343
  const startTime = Date.now();
344
+ // Register agent event listener for streaming (NEW METHOD)
345
+ let unsubscribeAgentEvents;
346
+ let runId;
347
+ let isCompleted = false;
348
+ if (streamingEnabled && hasAgentEventSupport) {
349
+ // Subscribe to agent events BEFORE dispatching the message
350
+ unsubscribeAgentEvents = onAgentEvent((evt) => {
351
+ if (isCompleted)
352
+ return;
353
+ // Get runId from first event
354
+ if (!runId && evt.runId) {
355
+ runId = evt.runId;
356
+ console.log(`XiaoYi: [AGENT EVENT] Registered runId: ${runId}`);
357
+ }
358
+ // Only process events for this run
359
+ if (runId && evt.runId !== runId)
360
+ return;
361
+ const elapsed = Date.now() - startTime;
362
+ // Handle streaming text events
363
+ if (evt.stream === "assistant" && typeof evt.data?.text === "string") {
364
+ const newText = evt.data.text;
365
+ if (!newText)
366
+ return;
367
+ // Calculate delta text
368
+ const previousLength = accumulatedText.length;
369
+ accumulatedText = newText;
370
+ const deltaText = newText.slice(previousLength);
371
+ console.log(`\n>>> XiaoYi: [AGENT EVENT] Stream frame #${++partialCount} at ${elapsed}ms`);
372
+ console.log(` Run ID: ${evt.runId}`);
373
+ console.log(` Delta length: ${deltaText.length} chars`);
374
+ console.log(` Total length: ${newText.length} chars`);
375
+ console.log(` Delta content: "${deltaText}"`);
376
+ // Send PARTIAL frame with delta content
377
+ const partialResponse = {
378
+ sessionId: message.sessionId,
379
+ messageId: `partial_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
380
+ timestamp: Date.now(),
381
+ agentId: resolvedAccount.config.agentId,
382
+ sender: {
383
+ id: resolvedAccount.config.agentId,
384
+ name: "OpenClaw Agent",
385
+ type: "agent",
386
+ },
387
+ content: {
388
+ type: "text",
389
+ text: deltaText || newText,
390
+ },
391
+ status: "processing",
392
+ };
393
+ const conn = runtime.getConnection();
394
+ if (conn) {
395
+ conn.sendResponse(partialResponse, taskId, message.sessionId, false, true)
396
+ .then(() => {
397
+ console.log(` ✓ SENT (isFinal=false, append=true)\n`);
398
+ })
399
+ .catch((err) => {
400
+ console.error(` ✗ Send failed: ${err}\n`);
401
+ });
402
+ }
403
+ }
404
+ // Handle lifecycle events (completion/error)
405
+ if (evt.stream === "lifecycle") {
406
+ const phase = evt.data?.phase;
407
+ if (phase === "end") {
408
+ isCompleted = true;
409
+ console.log("\n" + "-".repeat(60));
410
+ console.log(`XiaoYi: [AGENT EVENT] Lifecycle phase: end`);
411
+ console.log(` Run ID: ${evt.runId}`);
412
+ console.log(` Elapsed: ${elapsed}ms`);
413
+ console.log(` Total frames: ${partialCount}`);
414
+ console.log(` Final length: ${accumulatedText.length} chars`);
415
+ console.log("-".repeat(60) + "\n");
416
+ // Send FINAL frame
417
+ const response = {
418
+ sessionId: message.sessionId,
419
+ messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
420
+ timestamp: Date.now(),
421
+ agentId: resolvedAccount.config.agentId,
422
+ sender: {
423
+ id: resolvedAccount.config.agentId,
424
+ name: "OpenClaw Agent",
425
+ type: "agent",
426
+ },
427
+ content: {
428
+ type: "text",
429
+ text: accumulatedText,
430
+ },
431
+ status: "success",
432
+ };
433
+ const conn = runtime.getConnection();
434
+ if (conn) {
435
+ conn.sendResponse(response, taskId, message.sessionId, true, false)
436
+ .then(() => {
437
+ console.log(`✓ XiaoYi: FINAL frame sent (isFinal=true, append=false)\n`);
438
+ })
439
+ .catch((err) => {
440
+ console.error(`✗ Final send failed: ${err}\n`);
441
+ });
442
+ }
443
+ // Unsubscribe from events
444
+ if (unsubscribeAgentEvents) {
445
+ unsubscribeAgentEvents();
446
+ unsubscribeAgentEvents = undefined;
447
+ }
448
+ }
449
+ else if (phase === "error") {
450
+ isCompleted = true;
451
+ console.error(`XiaoYi: [AGENT EVENT] Error phase:`, evt.data?.error);
452
+ // Unsubscribe from events
453
+ if (unsubscribeAgentEvents) {
454
+ unsubscribeAgentEvents();
455
+ unsubscribeAgentEvents = undefined;
456
+ }
457
+ }
458
+ }
459
+ });
460
+ console.log(`XiaoYi: [AGENT EVENT] Listener registered for streaming`);
461
+ }
462
+ else if (streamingEnabled && !hasAgentEventSupport) {
463
+ console.warn(`XiaoYi: [WARN] Streaming enabled but onAgentEvent not available, falling back to non-streaming mode`);
464
+ }
326
465
  await pluginRuntime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
327
466
  ctx: msgContext,
328
467
  cfg: config,
329
468
  dispatcherOptions: {
330
469
  deliver: async (payload) => {
470
+ // This is the fallback for when streaming is disabled
471
+ if (streamingEnabled) {
472
+ // Streaming mode: deliver is handled by onAgentEvent
473
+ console.log(`XiaoYi: [DELIVER] Skipping (handled by onAgentEvent)`);
474
+ return;
475
+ }
331
476
  const elapsed = Date.now() - startTime;
332
477
  const completeText = payload.text || "";
333
478
  accumulatedText = completeText;
334
479
  console.log("\n" + "-".repeat(60));
335
- console.log(`XiaoYi: [STREAMING] DELIVER callback triggered`);
480
+ console.log(`XiaoYi: [DELIVER] Non-streaming mode`);
336
481
  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)}...`);
482
+ console.log(` Length: ${completeText.length} chars`);
341
483
  console.log("-".repeat(60) + "\n");
342
- // Send FINAL frame with complete content
484
+ // Send final frame
343
485
  const response = {
344
486
  sessionId: message.sessionId,
345
487
  messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
@@ -352,74 +494,34 @@ exports.xiaoyiPlugin = {
352
494
  },
353
495
  content: {
354
496
  type: "text",
355
- text: accumulatedText, // Complete content
497
+ text: accumulatedText,
356
498
  },
357
499
  status: "success",
358
500
  };
359
501
  const conn = runtime.getConnection();
360
502
  if (conn) {
361
- // Use append=false for final frame (complete content)
362
503
  await conn.sendResponse(response, taskId, message.sessionId, true, false);
363
- console.log(`✓ XiaoYi: FINAL frame sent (isFinal=true, append=false, length=${accumulatedText.length})\n`);
504
+ console.log(`✓ XiaoYi: FINAL frame sent (non-streaming)\n`);
364
505
  }
365
506
  },
366
507
  onIdle: async () => {
367
508
  const elapsed = Date.now() - startTime;
368
509
  console.log("\n" + "=".repeat(60));
369
- console.log(`XiaoYi: [STREAMING] Processing complete`);
510
+ console.log(`XiaoYi: [IDLE] Processing complete`);
370
511
  console.log(` Total time: ${elapsed}ms`);
371
- console.log(` Total frames: ${frameCount}`);
372
- console.log(` Partial frames: ${partialCount}`);
512
+ console.log(` Streaming frames: ${partialCount}`);
373
513
  console.log(` Final length: ${accumulatedText.length} chars`);
374
514
  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`);
515
+ // Cleanup event listener if still active
516
+ if (unsubscribeAgentEvents && !isCompleted) {
517
+ console.log(`XiaoYi: [IDLE] Cleaning up event listener`);
518
+ unsubscribeAgentEvents();
519
+ unsubscribeAgentEvents = undefined;
419
520
  }
420
521
  },
421
- } : undefined, // No replyOptions when streaming is disabled
422
- images: images.length > 0 ? images : undefined, // Pass images to AI processing
522
+ },
523
+ replyOptions: undefined, // NOT using onPartialReply anymore
524
+ images: images.length > 0 ? images : undefined,
423
525
  });
424
526
  }
425
527
  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.3",
4
4
  "description": "XiaoYi channel plugin for OpenClaw",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",