@ynhcj/xiaoyi 2.2.5 → 2.2.7

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
@@ -319,6 +319,49 @@ exports.xiaoyiPlugin = {
319
319
  const taskId = runtime.getTaskIdForSession(message.sessionId) || `task_${Date.now()}`;
320
320
  const startTime = Date.now();
321
321
  let accumulatedText = "";
322
+ // ==================== START TIMEOUT PROTECTION ====================
323
+ // Start 60-second timeout timer
324
+ const timeoutConfig = runtime.getTimeoutConfig();
325
+ console.log(`[TIMEOUT] Starting ${timeoutConfig.duration}ms timeout protection for session ${message.sessionId}`);
326
+ runtime.setTimeoutForSession(message.sessionId, async () => {
327
+ // Timeout callback - send timeout message to user
328
+ const elapsed = Date.now() - startTime;
329
+ console.log("\n" + "=".repeat(60));
330
+ console.log(`[TIMEOUT] Timeout triggered for session ${message.sessionId}`);
331
+ console.log(` Elapsed: ${elapsed}ms`);
332
+ console.log(` Task ID: ${taskId}`);
333
+ console.log("=".repeat(60) + "\n");
334
+ const conn = runtime.getConnection();
335
+ if (conn) {
336
+ const timeoutResponse = {
337
+ sessionId: message.sessionId,
338
+ messageId: `timeout_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
339
+ timestamp: Date.now(),
340
+ agentId: resolvedAccount.config.agentId,
341
+ sender: {
342
+ id: resolvedAccount.config.agentId,
343
+ name: "OpenClaw Agent",
344
+ type: "agent",
345
+ },
346
+ content: {
347
+ type: "text",
348
+ text: timeoutConfig.message,
349
+ },
350
+ status: "success",
351
+ };
352
+ try {
353
+ await conn.sendResponse(timeoutResponse, taskId, message.sessionId, true, false);
354
+ console.log(`[TIMEOUT] Timeout message sent successfully to session ${message.sessionId}\n`);
355
+ }
356
+ catch (error) {
357
+ console.error(`[TIMEOUT] Failed to send timeout message:`, error);
358
+ }
359
+ }
360
+ else {
361
+ console.error(`[TIMEOUT] Connection not available, cannot send timeout message\n`);
362
+ }
363
+ });
364
+ // ==================== END TIMEOUT PROTECTION ====================
322
365
  await pluginRuntime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
323
366
  ctx: msgContext,
324
367
  cfg: config,
@@ -327,6 +370,17 @@ exports.xiaoyiPlugin = {
327
370
  const elapsed = Date.now() - startTime;
328
371
  const completeText = payload.text || "";
329
372
  accumulatedText = completeText;
373
+ // ==================== CHECK TIMEOUT ====================
374
+ // If timeout already sent, discard this response
375
+ if (runtime.isSessionTimeout(message.sessionId)) {
376
+ console.log("\n" + "=".repeat(60));
377
+ console.log(`[TIMEOUT] Response received AFTER timeout`);
378
+ console.log(` Session: ${message.sessionId}`);
379
+ console.log(` Elapsed: ${elapsed}ms`);
380
+ console.log(` Action: DISCARDING (timeout message already sent)`);
381
+ console.log("=".repeat(60) + "\n");
382
+ return;
383
+ }
330
384
  console.log("\n" + "-".repeat(60));
331
385
  console.log(`XiaoYi: [DELIVER] AI response received`);
332
386
  console.log(` Elapsed: ${elapsed}ms`);
@@ -358,6 +412,8 @@ exports.xiaoyiPlugin = {
358
412
  else {
359
413
  console.error(`✗ XiaoYi: Connection not available\n`);
360
414
  }
415
+ // Clear timeout as response was sent successfully
416
+ runtime.markSessionCompleted(message.sessionId);
361
417
  },
362
418
  onIdle: async () => {
363
419
  const elapsed = Date.now() - startTime;
@@ -366,6 +422,8 @@ exports.xiaoyiPlugin = {
366
422
  console.log(` Total time: ${elapsed}ms`);
367
423
  console.log(` Final length: ${accumulatedText.length} chars`);
368
424
  console.log("=".repeat(60) + "\n");
425
+ // Clear timeout on completion
426
+ runtime.markSessionCompleted(message.sessionId);
369
427
  },
370
428
  },
371
429
  replyOptions: undefined,
@@ -374,6 +432,8 @@ exports.xiaoyiPlugin = {
374
432
  }
375
433
  catch (error) {
376
434
  console.error("XiaoYi: [ERROR] Error dispatching message:", error);
435
+ // Clear timeout on error
436
+ runtime.clearSessionTimeout(message.sessionId);
377
437
  }
378
438
  });
379
439
  // Setup cancel handler
@@ -33,8 +33,8 @@ export declare const XiaoYiConfigSchema: z.ZodObject<{
33
33
  agentId?: string | undefined;
34
34
  accounts?: Record<string, unknown> | undefined;
35
35
  }, {
36
- name?: string | undefined;
37
36
  enabled?: boolean | undefined;
37
+ name?: string | undefined;
38
38
  wsUrl?: string | undefined;
39
39
  ak?: string | undefined;
40
40
  sk?: string | undefined;
package/dist/runtime.d.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  import { XiaoYiWebSocketManager } from "./websocket";
2
2
  import { XiaoYiChannelConfig } from "./types";
3
+ /**
4
+ * Timeout configuration
5
+ */
6
+ export interface TimeoutConfig {
7
+ enabled: boolean;
8
+ duration: number;
9
+ message: string;
10
+ }
3
11
  /**
4
12
  * Runtime state for XiaoYi channel
5
13
  * Manages single WebSocket connection (single account mode)
@@ -10,6 +18,9 @@ export declare class XiaoYiRuntime {
10
18
  private config;
11
19
  private sessionToTaskIdMap;
12
20
  private instanceId;
21
+ private sessionTimeoutMap;
22
+ private sessionTimeoutSent;
23
+ private timeoutConfig;
13
24
  constructor();
14
25
  getInstanceId(): string;
15
26
  /**
@@ -28,6 +39,40 @@ export declare class XiaoYiRuntime {
28
39
  * Stop connection
29
40
  */
30
41
  stop(): void;
42
+ /**
43
+ * Set timeout configuration
44
+ */
45
+ setTimeoutConfig(config: Partial<TimeoutConfig>): void;
46
+ /**
47
+ * Get timeout configuration
48
+ */
49
+ getTimeoutConfig(): TimeoutConfig;
50
+ /**
51
+ * Set timeout for a session
52
+ * @param sessionId - Session ID
53
+ * @param callback - Function to call when timeout occurs
54
+ * @returns The timeout ID (for cancellation)
55
+ */
56
+ setTimeoutForSession(sessionId: string, callback: () => void): NodeJS.Timeout | undefined;
57
+ /**
58
+ * Clear timeout for a session
59
+ * @param sessionId - Session ID
60
+ */
61
+ clearSessionTimeout(sessionId: string): void;
62
+ /**
63
+ * Check if timeout has been sent for a session
64
+ * @param sessionId - Session ID
65
+ */
66
+ isSessionTimeout(sessionId: string): boolean;
67
+ /**
68
+ * Mark session as completed (clear timeout and timeout flag)
69
+ * @param sessionId - Session ID
70
+ */
71
+ markSessionCompleted(sessionId: string): void;
72
+ /**
73
+ * Clear all timeouts
74
+ */
75
+ clearAllTimeouts(): void;
31
76
  /**
32
77
  * Get WebSocket manager
33
78
  */
package/dist/runtime.js CHANGED
@@ -4,6 +4,14 @@ exports.XiaoYiRuntime = void 0;
4
4
  exports.getXiaoYiRuntime = getXiaoYiRuntime;
5
5
  exports.setXiaoYiRuntime = setXiaoYiRuntime;
6
6
  const websocket_1 = require("./websocket");
7
+ /**
8
+ * Default timeout configuration
9
+ */
10
+ const DEFAULT_TIMEOUT_CONFIG = {
11
+ enabled: true,
12
+ duration: 60000, // 60 seconds
13
+ message: "任务还在处理中,请稍后回来查看",
14
+ };
7
15
  /**
8
16
  * Runtime state for XiaoYi channel
9
17
  * Manages single WebSocket connection (single account mode)
@@ -14,6 +22,10 @@ class XiaoYiRuntime {
14
22
  this.pluginRuntime = null; // Store PluginRuntime from OpenClaw
15
23
  this.config = null;
16
24
  this.sessionToTaskIdMap = new Map(); // Map sessionId to taskId
25
+ // Timeout management
26
+ this.sessionTimeoutMap = new Map();
27
+ this.sessionTimeoutSent = new Set();
28
+ this.timeoutConfig = DEFAULT_TIMEOUT_CONFIG;
17
29
  this.instanceId = `runtime_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
18
30
  console.log(`XiaoYi: Created new runtime instance: ${this.instanceId}`);
19
31
  }
@@ -73,6 +85,91 @@ class XiaoYiRuntime {
73
85
  }
74
86
  // Clear session mappings
75
87
  this.sessionToTaskIdMap.clear();
88
+ // Clear all timeouts
89
+ this.clearAllTimeouts();
90
+ }
91
+ /**
92
+ * Set timeout configuration
93
+ */
94
+ setTimeoutConfig(config) {
95
+ this.timeoutConfig = { ...this.timeoutConfig, ...config };
96
+ console.log(`XiaoYi: Timeout config updated:`, this.timeoutConfig);
97
+ }
98
+ /**
99
+ * Get timeout configuration
100
+ */
101
+ getTimeoutConfig() {
102
+ return { ...this.timeoutConfig };
103
+ }
104
+ /**
105
+ * Set timeout for a session
106
+ * @param sessionId - Session ID
107
+ * @param callback - Function to call when timeout occurs
108
+ * @returns The timeout ID (for cancellation)
109
+ */
110
+ setTimeoutForSession(sessionId, callback) {
111
+ if (!this.timeoutConfig.enabled) {
112
+ console.log(`[TIMEOUT] Timeout disabled, skipping for session ${sessionId}`);
113
+ return undefined;
114
+ }
115
+ // Clear existing timeout AND timeout flag if any (reuse session scenario)
116
+ const hadExistingTimeout = this.sessionTimeoutMap.has(sessionId);
117
+ const hadSentTimeout = this.sessionTimeoutSent.has(sessionId);
118
+ this.clearSessionTimeout(sessionId);
119
+ // Also clear the timeout sent flag to allow this session to timeout again
120
+ if (hadSentTimeout) {
121
+ this.sessionTimeoutSent.delete(sessionId);
122
+ console.log(`[TIMEOUT] Previous timeout flag cleared for session ${sessionId} (session reuse)`);
123
+ }
124
+ const timeoutId = setTimeout(() => {
125
+ console.log(`[TIMEOUT] Timeout triggered for session ${sessionId}`);
126
+ this.sessionTimeoutMap.delete(sessionId);
127
+ this.sessionTimeoutSent.add(sessionId);
128
+ callback();
129
+ }, this.timeoutConfig.duration);
130
+ this.sessionTimeoutMap.set(sessionId, timeoutId);
131
+ const logSuffix = hadExistingTimeout ? " (replacing existing timeout)" : "";
132
+ console.log(`[TIMEOUT] ${this.timeoutConfig.duration}ms timeout started for session ${sessionId}${logSuffix}`);
133
+ return timeoutId;
134
+ }
135
+ /**
136
+ * Clear timeout for a session
137
+ * @param sessionId - Session ID
138
+ */
139
+ clearSessionTimeout(sessionId) {
140
+ const timeoutId = this.sessionTimeoutMap.get(sessionId);
141
+ if (timeoutId) {
142
+ clearTimeout(timeoutId);
143
+ this.sessionTimeoutMap.delete(sessionId);
144
+ console.log(`[TIMEOUT] Timeout cleared for session ${sessionId}`);
145
+ }
146
+ }
147
+ /**
148
+ * Check if timeout has been sent for a session
149
+ * @param sessionId - Session ID
150
+ */
151
+ isSessionTimeout(sessionId) {
152
+ return this.sessionTimeoutSent.has(sessionId);
153
+ }
154
+ /**
155
+ * Mark session as completed (clear timeout and timeout flag)
156
+ * @param sessionId - Session ID
157
+ */
158
+ markSessionCompleted(sessionId) {
159
+ this.clearSessionTimeout(sessionId);
160
+ this.sessionTimeoutSent.delete(sessionId);
161
+ console.log(`[TIMEOUT] Session ${sessionId} marked as completed`);
162
+ }
163
+ /**
164
+ * Clear all timeouts
165
+ */
166
+ clearAllTimeouts() {
167
+ for (const [sessionId, timeoutId] of this.sessionTimeoutMap.entries()) {
168
+ clearTimeout(timeoutId);
169
+ }
170
+ this.sessionTimeoutMap.clear();
171
+ this.sessionTimeoutSent.clear();
172
+ console.log("[TIMEOUT] All timeouts cleared");
76
173
  }
77
174
  /**
78
175
  * Get WebSocket manager
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
4
4
  "description": "XiaoYi channel plugin for OpenClaw",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",