@ynhcj/xiaoyi 2.3.4 → 2.3.6

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
@@ -215,7 +215,7 @@ exports.xiaoyiPlugin = {
215
215
  const runtime = getXiaoYiRuntime();
216
216
  console.log(`XiaoYi: [Message Handler] Using runtime instance: ${runtime.getInstanceId()}`);
217
217
  // Store sessionId -> taskId mapping in runtime (use params.id as taskId)
218
- runtime.setTaskIdForSession(message.sessionId, message.params.id);
218
+ runtime.setTaskIdForSession(message.params.sessionId, message.params.id);
219
219
  // Get PluginRuntime from our runtime wrapper
220
220
  const pluginRuntime = runtime.getPluginRuntime();
221
221
  if (!pluginRuntime) {
@@ -291,8 +291,8 @@ exports.xiaoyiPlugin = {
291
291
  const msgContext = {
292
292
  Body: bodyText,
293
293
  From: senderId,
294
- To: message.sessionId,
295
- SessionKey: `xiaoyi:${resolvedAccount.accountId}:${message.sessionId}`,
294
+ To: message.params.sessionId,
295
+ SessionKey: `xiaoyi:${resolvedAccount.accountId}:${message.params.sessionId}`,
296
296
  AccountId: resolvedAccount.accountId,
297
297
  MessageSid: message.id, // Use top-level id as message sequence number
298
298
  Timestamp: Date.now(), // Generate timestamp since new format doesn't include it
@@ -307,30 +307,34 @@ exports.xiaoyiPlugin = {
307
307
  try {
308
308
  console.log("\n" + "=".repeat(60));
309
309
  console.log(`XiaoYi: [MESSAGE] Processing user message`);
310
- console.log(` Session: ${message.sessionId}`);
310
+ console.log(` Session: ${message.params.sessionId}`);
311
311
  console.log(` Task ID: ${message.params.id}`);
312
312
  console.log(` User input: ${bodyText.substring(0, 50)}${bodyText.length > 50 ? "..." : ""}`);
313
313
  console.log(` Images: ${images.length}`);
314
314
  console.log("=".repeat(60) + "\n");
315
- const taskId = runtime.getTaskIdForSession(message.sessionId) || `task_${Date.now()}`;
315
+ const taskId = runtime.getTaskIdForSession(message.params.sessionId) || `task_${Date.now()}`;
316
316
  const startTime = Date.now();
317
317
  let accumulatedText = "";
318
+ // ==================== CREATE ABORT CONTROLLER ====================
319
+ // Create AbortController for this session to allow cancelation
320
+ const { controller: abortController, signal: abortSignal } = runtime.createAbortControllerForSession(message.params.sessionId);
321
+ // ================================================================
318
322
  // ==================== START TIMEOUT PROTECTION ====================
319
323
  // Start 60-second timeout timer
320
324
  const timeoutConfig = runtime.getTimeoutConfig();
321
- console.log(`[TIMEOUT] Starting ${timeoutConfig.duration}ms timeout protection for session ${message.sessionId}`);
322
- runtime.setTimeoutForSession(message.sessionId, async () => {
325
+ console.log(`[TIMEOUT] Starting ${timeoutConfig.duration}ms timeout protection for session ${message.params.sessionId}`);
326
+ runtime.setTimeoutForSession(message.params.sessionId, async () => {
323
327
  // Timeout callback - send timeout message to user
324
328
  const elapsed = Date.now() - startTime;
325
329
  console.log("\n" + "=".repeat(60));
326
- console.log(`[TIMEOUT] Timeout triggered for session ${message.sessionId}`);
330
+ console.log(`[TIMEOUT] Timeout triggered for session ${message.params.sessionId}`);
327
331
  console.log(` Elapsed: ${elapsed}ms`);
328
332
  console.log(` Task ID: ${taskId}`);
329
333
  console.log("=".repeat(60) + "\n");
330
334
  const conn = runtime.getConnection();
331
335
  if (conn) {
332
336
  const timeoutResponse = {
333
- sessionId: message.sessionId,
337
+ sessionId: message.params.sessionId,
334
338
  messageId: `timeout_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
335
339
  timestamp: Date.now(),
336
340
  agentId: resolvedAccount.config.agentId,
@@ -346,8 +350,8 @@ exports.xiaoyiPlugin = {
346
350
  status: "success",
347
351
  };
348
352
  try {
349
- await conn.sendResponse(timeoutResponse, taskId, message.sessionId, true, false);
350
- console.log(`[TIMEOUT] Timeout message sent successfully to session ${message.sessionId}\n`);
353
+ await conn.sendResponse(timeoutResponse, taskId, message.params.sessionId, true, false);
354
+ console.log(`[TIMEOUT] Timeout message sent successfully to session ${message.params.sessionId}\n`);
351
355
  }
352
356
  catch (error) {
353
357
  console.error(`[TIMEOUT] Failed to send timeout message:`, error);
@@ -366,12 +370,22 @@ exports.xiaoyiPlugin = {
366
370
  const elapsed = Date.now() - startTime;
367
371
  const completeText = payload.text || "";
368
372
  accumulatedText = completeText;
373
+ // Check if session was aborted
374
+ if (runtime.isSessionAborted(message.params.sessionId)) {
375
+ console.log("\n" + "=".repeat(60));
376
+ console.log(`[ABORT] Response received AFTER abort`);
377
+ console.log(` Session: ${message.params.sessionId}`);
378
+ console.log(` Elapsed: ${elapsed}ms`);
379
+ console.log(` Action: DISCARDING (session was canceled)`);
380
+ console.log("=".repeat(60) + "\n");
381
+ return;
382
+ }
369
383
  // ==================== CHECK TIMEOUT ====================
370
384
  // If timeout already sent, discard this response
371
- if (runtime.isSessionTimeout(message.sessionId)) {
385
+ if (runtime.isSessionTimeout(message.params.sessionId)) {
372
386
  console.log("\n" + "=".repeat(60));
373
387
  console.log(`[TIMEOUT] Response received AFTER timeout`);
374
- console.log(` Session: ${message.sessionId}`);
388
+ console.log(` Session: ${message.params.sessionId}`);
375
389
  console.log(` Elapsed: ${elapsed}ms`);
376
390
  console.log(` Action: DISCARDING (timeout message already sent)`);
377
391
  console.log("=".repeat(60) + "\n");
@@ -382,7 +396,7 @@ exports.xiaoyiPlugin = {
382
396
  if (!completeText || completeText.length === 0) {
383
397
  console.log("\n" + "=".repeat(60));
384
398
  console.log(`[TIMEOUT] Empty response detected`);
385
- console.log(` Session: ${message.sessionId}`);
399
+ console.log(` Session: ${message.params.sessionId}`);
386
400
  console.log(` Elapsed: ${elapsed}ms`);
387
401
  console.log(` Action: KEEPING TIMEOUT (session conflict detected)`);
388
402
  console.log("=".repeat(60) + "\n");
@@ -397,7 +411,7 @@ exports.xiaoyiPlugin = {
397
411
  console.log("-".repeat(60) + "\n");
398
412
  // Send final response to XiaoYi
399
413
  const response = {
400
- sessionId: message.sessionId,
414
+ sessionId: message.params.sessionId,
401
415
  messageId: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
402
416
  timestamp: Date.now(),
403
417
  agentId: resolvedAccount.config.agentId,
@@ -414,14 +428,14 @@ exports.xiaoyiPlugin = {
414
428
  };
415
429
  const conn = runtime.getConnection();
416
430
  if (conn) {
417
- await conn.sendResponse(response, taskId, message.sessionId, true, false);
431
+ await conn.sendResponse(response, taskId, message.params.sessionId, true, false);
418
432
  console.log(`✓ XiaoYi: Response sent successfully\n`);
419
433
  }
420
434
  else {
421
435
  console.error(`✗ XiaoYi: Connection not available\n`);
422
436
  }
423
437
  // Clear timeout as response was sent successfully
424
- runtime.markSessionCompleted(message.sessionId);
438
+ runtime.markSessionCompleted(message.params.sessionId);
425
439
  },
426
440
  onIdle: async () => {
427
441
  const elapsed = Date.now() - startTime;
@@ -432,7 +446,8 @@ exports.xiaoyiPlugin = {
432
446
  // Only clear timeout if we have a valid response
433
447
  // If empty, keep timeout running to handle session conflict
434
448
  if (accumulatedText.length > 0) {
435
- runtime.markSessionCompleted(message.sessionId);
449
+ runtime.markSessionCompleted(message.params.sessionId);
450
+ runtime.clearAbortControllerForSession(message.params.sessionId);
436
451
  console.log(`[TIMEOUT] Timeout cleared (valid response)\n`);
437
452
  }
438
453
  else {
@@ -441,26 +456,39 @@ exports.xiaoyiPlugin = {
441
456
  console.log("=".repeat(60) + "\n");
442
457
  },
443
458
  },
444
- replyOptions: undefined,
459
+ replyOptions: {
460
+ abortSignal: abortSignal, // Pass abort signal to allow cancellation
461
+ },
445
462
  images: images.length > 0 ? images : undefined,
446
463
  });
447
464
  }
448
465
  catch (error) {
449
466
  console.error("XiaoYi: [ERROR] Error dispatching message:", error);
450
467
  // Clear timeout on error
451
- runtime.clearSessionTimeout(message.sessionId);
468
+ runtime.clearSessionTimeout(message.params.sessionId);
469
+ // Clear abort controller on error
470
+ runtime.clearAbortControllerForSession(message.params.sessionId);
452
471
  }
453
472
  });
454
473
  // Setup cancel handler
455
- // Note: The response is already sent in websocket.ts, this is just for logging
474
+ // When tasks/cancel is received, abort the current session's agent run
456
475
  connection.on("cancel", async (data) => {
476
+ const { sessionId } = data;
457
477
  console.log("\n" + "=".repeat(60));
458
- console.log(`XiaoYi: [CANCEL] Cancel event received (response already sent)`);
459
- console.log(` Session: ${data.sessionId}`);
478
+ console.log(`XiaoYi: [CANCEL] Cancel event received`);
479
+ console.log(` Session: ${sessionId}`);
460
480
  console.log(` Task ID: ${data.taskId || "N/A"}`);
461
481
  console.log("=".repeat(60) + "\n");
462
- // NOTE: The cancel response has already been sent to XiaoYi in websocket.ts.
463
- // Actual cancellation of the agent run depends on OpenClaw's internal mechanisms.
482
+ // Abort the session's agent run
483
+ const aborted = runtime.abortSession(sessionId);
484
+ if (aborted) {
485
+ console.log(`[CANCEL] Successfully triggered abort for session ${sessionId}`);
486
+ }
487
+ else {
488
+ console.log(`[CANCEL] No active agent run found for session ${sessionId}`);
489
+ }
490
+ // Clear timeout as the session is being canceled
491
+ runtime.markSessionCompleted(sessionId);
464
492
  });
465
493
  console.log("XiaoYi: Event handlers registered");
466
494
  console.log("XiaoYi: startAccount() completed - END");
package/dist/runtime.d.ts CHANGED
@@ -21,6 +21,7 @@ export declare class XiaoYiRuntime {
21
21
  private sessionTimeoutMap;
22
22
  private sessionTimeoutSent;
23
23
  private timeoutConfig;
24
+ private sessionAbortControllerMap;
24
25
  constructor();
25
26
  getInstanceId(): string;
26
27
  /**
@@ -97,6 +98,36 @@ export declare class XiaoYiRuntime {
97
98
  * Clear taskId for a session
98
99
  */
99
100
  clearTaskIdForSession(sessionId: string): void;
101
+ /**
102
+ * Create and register an AbortController for a session
103
+ * @param sessionId - Session ID
104
+ * @returns The AbortController and its signal
105
+ */
106
+ createAbortControllerForSession(sessionId: string): {
107
+ controller: AbortController;
108
+ signal: AbortSignal;
109
+ };
110
+ /**
111
+ * Abort a session's agent run
112
+ * @param sessionId - Session ID
113
+ * @returns true if a controller was found and aborted, false otherwise
114
+ */
115
+ abortSession(sessionId: string): boolean;
116
+ /**
117
+ * Check if a session has been aborted
118
+ * @param sessionId - Session ID
119
+ * @returns true if the session's abort signal was triggered
120
+ */
121
+ isSessionAborted(sessionId: string): boolean;
122
+ /**
123
+ * Clear the AbortController for a session (call when agent completes successfully)
124
+ * @param sessionId - Session ID
125
+ */
126
+ clearAbortControllerForSession(sessionId: string): void;
127
+ /**
128
+ * Clear all AbortControllers
129
+ */
130
+ clearAllAbortControllers(): void;
100
131
  }
101
132
  export declare function getXiaoYiRuntime(): XiaoYiRuntime;
102
133
  export declare function setXiaoYiRuntime(runtime: any): void;
package/dist/runtime.js CHANGED
@@ -26,6 +26,8 @@ class XiaoYiRuntime {
26
26
  this.sessionTimeoutMap = new Map();
27
27
  this.sessionTimeoutSent = new Set();
28
28
  this.timeoutConfig = DEFAULT_TIMEOUT_CONFIG;
29
+ // AbortController management for canceling agent runs
30
+ this.sessionAbortControllerMap = new Map();
29
31
  this.instanceId = `runtime_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
30
32
  console.log(`XiaoYi: Created new runtime instance: ${this.instanceId}`);
31
33
  }
@@ -100,6 +102,8 @@ class XiaoYiRuntime {
100
102
  this.sessionToTaskIdMap.clear();
101
103
  // Clear all timeouts
102
104
  this.clearAllTimeouts();
105
+ // Clear all abort controllers
106
+ this.clearAllAbortControllers();
103
107
  }
104
108
  /**
105
109
  * Set timeout configuration
@@ -220,6 +224,62 @@ class XiaoYiRuntime {
220
224
  clearTaskIdForSession(sessionId) {
221
225
  this.sessionToTaskIdMap.delete(sessionId);
222
226
  }
227
+ /**
228
+ * Create and register an AbortController for a session
229
+ * @param sessionId - Session ID
230
+ * @returns The AbortController and its signal
231
+ */
232
+ createAbortControllerForSession(sessionId) {
233
+ // Abort any existing controller for this session
234
+ this.abortSession(sessionId);
235
+ const controller = new AbortController();
236
+ this.sessionAbortControllerMap.set(sessionId, controller);
237
+ console.log(`[ABORT] Created AbortController for session ${sessionId}`);
238
+ return { controller, signal: controller.signal };
239
+ }
240
+ /**
241
+ * Abort a session's agent run
242
+ * @param sessionId - Session ID
243
+ * @returns true if a controller was found and aborted, false otherwise
244
+ */
245
+ abortSession(sessionId) {
246
+ const controller = this.sessionAbortControllerMap.get(sessionId);
247
+ if (controller) {
248
+ console.log(`[ABORT] Aborting session ${sessionId}`);
249
+ controller.abort();
250
+ this.sessionAbortControllerMap.delete(sessionId);
251
+ return true;
252
+ }
253
+ console.log(`[ABORT] No AbortController found for session ${sessionId}`);
254
+ return false;
255
+ }
256
+ /**
257
+ * Check if a session has been aborted
258
+ * @param sessionId - Session ID
259
+ * @returns true if the session's abort signal was triggered
260
+ */
261
+ isSessionAborted(sessionId) {
262
+ const controller = this.sessionAbortControllerMap.get(sessionId);
263
+ return controller ? controller.signal.aborted : false;
264
+ }
265
+ /**
266
+ * Clear the AbortController for a session (call when agent completes successfully)
267
+ * @param sessionId - Session ID
268
+ */
269
+ clearAbortControllerForSession(sessionId) {
270
+ const controller = this.sessionAbortControllerMap.get(sessionId);
271
+ if (controller) {
272
+ this.sessionAbortControllerMap.delete(sessionId);
273
+ console.log(`[ABORT] Cleared AbortController for session ${sessionId}`);
274
+ }
275
+ }
276
+ /**
277
+ * Clear all AbortControllers
278
+ */
279
+ clearAllAbortControllers() {
280
+ this.sessionAbortControllerMap.clear();
281
+ console.log("[ABORT] All AbortControllers cleared");
282
+ }
223
283
  }
224
284
  exports.XiaoYiRuntime = XiaoYiRuntime;
225
285
  // Global runtime instance - use global object to survive module reloads
package/dist/types.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  export interface A2ARequestMessage {
2
2
  agentId: string;
3
- sessionId: string;
4
3
  jsonrpc: "2.0";
5
4
  id: string;
6
5
  method: "message/stream";
7
6
  params: {
8
7
  id: string;
9
- sessionId?: string;
8
+ sessionId: string;
10
9
  agentLoginSessionId?: string;
11
10
  message: {
12
11
  role: "user" | "agent";
package/dist/websocket.js CHANGED
@@ -320,9 +320,9 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
320
320
  return;
321
321
  }
322
322
  // Record session → server mapping
323
- if (message.sessionId) {
324
- this.sessionServerMap.set(message.sessionId, sourceServer);
325
- console.log(`[MAP] Session ${message.sessionId} -> ${sourceServer}`);
323
+ if (message.params.sessionId) {
324
+ this.sessionServerMap.set(message.params.sessionId, sourceServer);
325
+ console.log(`[MAP] Session ${message.params.sessionId} -> ${sourceServer}`);
326
326
  }
327
327
  // Handle special messages (clearContext, tasks/cancel)
328
328
  if (message.method === "clearContext") {
@@ -341,7 +341,7 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
341
341
  if (this.isA2ARequestMessage(message)) {
342
342
  // Store task for potential cancellation
343
343
  this.activeTasks.set(message.id, {
344
- sessionId: message.sessionId,
344
+ sessionId: message.params.sessionId,
345
345
  timestamp: Date.now(),
346
346
  });
347
347
  // Emit with server info
@@ -476,16 +476,16 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
476
476
  * Handle clearContext method
477
477
  */
478
478
  handleClearContext(message, sourceServer) {
479
- console.log(`[${sourceServer}] Received clearContext for session: ${message.sessionId}`);
480
- this.sendClearContextResponse(message.id, message.sessionId, true, sourceServer)
479
+ console.log(`[${sourceServer}] Received clearContext for session: ${message.params.sessionId}`);
480
+ this.sendClearContextResponse(message.id, message.params.sessionId, true, sourceServer)
481
481
  .catch(error => console.error(`[${sourceServer}] Failed to send clearContext response:`, error));
482
482
  this.emit("clear", {
483
- sessionId: message.sessionId,
483
+ sessionId: message.params.sessionId,
484
484
  id: message.id,
485
485
  serverId: sourceServer,
486
486
  });
487
487
  // Remove session mapping
488
- this.sessionServerMap.delete(message.sessionId);
488
+ this.sessionServerMap.delete(message.params.sessionId);
489
489
  }
490
490
  /**
491
491
  * Handle clear message (legacy format)
@@ -770,14 +770,12 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
770
770
  isA2ARequestMessage(data) {
771
771
  return data &&
772
772
  typeof data.agentId === "string" &&
773
- typeof data.sessionId === "string" &&
774
773
  data.jsonrpc === "2.0" &&
775
774
  typeof data.id === "string" &&
776
775
  data.method === "message/stream" &&
777
776
  data.params &&
778
777
  typeof data.params.id === "string" &&
779
- // params.sessionId is optional - not sent by actual server
780
- (data.params.sessionId === undefined || typeof data.params.sessionId === "string") &&
778
+ typeof data.params.sessionId === "string" &&
781
779
  data.params.message &&
782
780
  typeof data.params.message.role === "string" &&
783
781
  Array.isArray(data.params.message.parts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi",
3
- "version": "2.3.4",
3
+ "version": "2.3.6",
4
4
  "description": "XiaoYi channel plugin for OpenClaw",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",