@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 +99 -53
- package/dist/websocket.d.ts +5 -0
- package/dist/websocket.js +58 -0
- package/package.json +6 -1
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
|
|
362
|
-
await conn.
|
|
363
|
-
console.log(`[TIMEOUT]
|
|
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
|
|
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
|
|
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(`[
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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;
|
package/dist/websocket.d.ts
CHANGED
|
@@ -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
|
+
"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": {
|