@ynhcj/xiaoyi 2.5.4 → 2.5.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.
@@ -8,8 +8,10 @@ export declare const XiaoYiConfigSchema: z.ZodObject<{
8
8
  name: z.ZodOptional<z.ZodString>;
9
9
  /** Whether this channel is enabled */
10
10
  enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
11
- /** WebSocket URL for A2A connection */
12
- wsUrl: z.ZodOptional<z.ZodString>;
11
+ /** First WebSocket server URL */
12
+ wsUrl1: z.ZodDefault<z.ZodOptional<z.ZodString>>;
13
+ /** Second WebSocket server URL */
14
+ wsUrl2: z.ZodDefault<z.ZodOptional<z.ZodString>>;
13
15
  /** Access Key for authentication */
14
16
  ak: z.ZodOptional<z.ZodString>;
15
17
  /** Secret Key for authentication */
@@ -18,27 +20,25 @@ export declare const XiaoYiConfigSchema: z.ZodObject<{
18
20
  agentId: z.ZodOptional<z.ZodString>;
19
21
  /** Enable debug logging */
20
22
  debug: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
21
- /** Enable streaming responses (default: false) */
22
- enableStreaming: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
23
23
  /** Multi-account configuration */
24
24
  accounts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
25
25
  }, "strip", z.ZodTypeAny, {
26
26
  enabled?: boolean;
27
- wsUrl?: string;
27
+ wsUrl1?: string;
28
+ wsUrl2?: string;
28
29
  ak?: string;
29
30
  sk?: string;
30
31
  agentId?: string;
31
- enableStreaming?: boolean;
32
32
  name?: string;
33
33
  debug?: boolean;
34
34
  accounts?: Record<string, unknown>;
35
35
  }, {
36
36
  enabled?: boolean;
37
- wsUrl?: string;
37
+ wsUrl1?: string;
38
+ wsUrl2?: string;
38
39
  ak?: string;
39
40
  sk?: string;
40
41
  agentId?: string;
41
- enableStreaming?: boolean;
42
42
  name?: string;
43
43
  debug?: boolean;
44
44
  accounts?: Record<string, unknown>;
@@ -10,9 +10,11 @@ exports.XiaoYiConfigSchema = zod_1.z.object({
10
10
  /** Account name (optional display name) */
11
11
  name: zod_1.z.string().optional(),
12
12
  /** Whether this channel is enabled */
13
- enabled: zod_1.z.boolean().optional().default(true),
14
- /** WebSocket URL for A2A connection */
15
- wsUrl: zod_1.z.string().optional(),
13
+ enabled: zod_1.z.boolean().optional().default(false),
14
+ /** First WebSocket server URL */
15
+ wsUrl1: zod_1.z.string().optional().default("wss://hag.cloud.huawei.com/openclaw/v1/ws/link"),
16
+ /** Second WebSocket server URL */
17
+ wsUrl2: zod_1.z.string().optional().default("wss://116.63.174.231/openclaw/v1/ws/link"),
16
18
  /** Access Key for authentication */
17
19
  ak: zod_1.z.string().optional(),
18
20
  /** Secret Key for authentication */
@@ -21,8 +23,6 @@ exports.XiaoYiConfigSchema = zod_1.z.object({
21
23
  agentId: zod_1.z.string().optional(),
22
24
  /** Enable debug logging */
23
25
  debug: zod_1.z.boolean().optional().default(false),
24
- /** Enable streaming responses (default: false) */
25
- enableStreaming: zod_1.z.boolean().optional().default(false),
26
26
  /** Multi-account configuration */
27
27
  accounts: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
28
28
  });
package/dist/index.d.ts CHANGED
@@ -14,13 +14,10 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
14
14
  * "wsUrl2": "ws://localhost:8766/ws/link",
15
15
  * "ak": "test_ak",
16
16
  * "sk": "test_sk",
17
- * "agentId": "your-agent-id",
18
- * "enableStreaming": true
17
+ * "agentId": "your-agent-id"
19
18
  * }
20
19
  * }
21
20
  * }
22
- *
23
- * Backward compatibility: Can use "wsUrl" instead of "wsUrl1" (wsUrl2 will use default)
24
21
  */
25
22
  declare const plugin: {
26
23
  id: string;
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const plugin_sdk_1 = require("openclaw/plugin-sdk");
4
3
  const channel_1 = require("./channel");
5
4
  const runtime_1 = require("./runtime");
6
5
  /**
@@ -18,19 +17,16 @@ const runtime_1 = require("./runtime");
18
17
  * "wsUrl2": "ws://localhost:8766/ws/link",
19
18
  * "ak": "test_ak",
20
19
  * "sk": "test_sk",
21
- * "agentId": "your-agent-id",
22
- * "enableStreaming": true
20
+ * "agentId": "your-agent-id"
23
21
  * }
24
22
  * }
25
23
  * }
26
- *
27
- * Backward compatibility: Can use "wsUrl" instead of "wsUrl1" (wsUrl2 will use default)
28
24
  */
29
25
  const plugin = {
30
26
  id: "xiaoyi",
31
27
  name: "XiaoYi Channel",
32
28
  description: "XiaoYi channel plugin with A2A protocol support",
33
- configSchema: (0, plugin_sdk_1.emptyPluginConfigSchema)(),
29
+ configSchema: undefined,
34
30
  register(api) {
35
31
  console.log("XiaoYi: register() called - START");
36
32
  // Set runtime for managing WebSocket connections
package/dist/push.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { XiaoYiChannelConfig } from "./types";
2
+ /**
3
+ * Push message sending service
4
+ * Sends notifications to XiaoYi clients via webhook API
5
+ */
6
+ export declare class XiaoYiPushService {
7
+ private config;
8
+ private readonly pushUrl;
9
+ constructor(config: XiaoYiChannelConfig);
10
+ /**
11
+ * Check if push functionality is configured
12
+ */
13
+ isConfigured(): boolean;
14
+ /**
15
+ * Generate HMAC-SHA256 signature
16
+ */
17
+ private generateSignature;
18
+ /**
19
+ * Generate UUID
20
+ */
21
+ private generateUUID;
22
+ /**
23
+ * Send push notification (with summary text)
24
+ * @param text - Summary text to send (e.g., first 30 characters)
25
+ * @param pushText - Push notification message (e.g., "任务已完成:xxx...")
26
+ */
27
+ sendPush(text: string, pushText: string): Promise<boolean>;
28
+ }
package/dist/push.js ADDED
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.XiaoYiPushService = void 0;
37
+ const crypto = __importStar(require("crypto"));
38
+ /**
39
+ * Push message sending service
40
+ * Sends notifications to XiaoYi clients via webhook API
41
+ */
42
+ class XiaoYiPushService {
43
+ constructor(config) {
44
+ this.pushUrl = "https://hag.cloud.huawei.com/open-ability-agent/v1/agent-webhook";
45
+ this.config = config;
46
+ }
47
+ /**
48
+ * Check if push functionality is configured
49
+ */
50
+ isConfigured() {
51
+ return Boolean(this.config.apiId?.trim() &&
52
+ this.config.pushId?.trim() &&
53
+ this.config.ak?.trim() &&
54
+ this.config.sk?.trim());
55
+ }
56
+ /**
57
+ * Generate HMAC-SHA256 signature
58
+ */
59
+ generateSignature(timestamp) {
60
+ const hmac = crypto.createHmac("sha256", this.config.sk);
61
+ hmac.update(timestamp);
62
+ return hmac.digest().toString("base64");
63
+ }
64
+ /**
65
+ * Generate UUID
66
+ */
67
+ generateUUID() {
68
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
69
+ const r = (Math.random() * 16) | 0;
70
+ const v = c === "x" ? r : (r & 0x3) | 0x8;
71
+ return v.toString(16);
72
+ });
73
+ }
74
+ /**
75
+ * Send push notification (with summary text)
76
+ * @param text - Summary text to send (e.g., first 30 characters)
77
+ * @param pushText - Push notification message (e.g., "任务已完成:xxx...")
78
+ */
79
+ async sendPush(text, pushText) {
80
+ if (!this.isConfigured()) {
81
+ console.log("[PUSH] Push not configured, skipping");
82
+ return false;
83
+ }
84
+ try {
85
+ const timestamp = Date.now().toString();
86
+ const signature = this.generateSignature(timestamp);
87
+ const messageId = this.generateUUID();
88
+ const payload = {
89
+ jsonrpc: "2.0",
90
+ id: messageId,
91
+ result: {
92
+ id: this.generateUUID(),
93
+ apiId: this.config.apiId,
94
+ pushId: this.config.pushId,
95
+ pushText: pushText,
96
+ kind: "task",
97
+ artifacts: [{
98
+ artifactId: this.generateUUID(),
99
+ parts: [{
100
+ kind: "text",
101
+ text: text, // Summary text
102
+ }]
103
+ }],
104
+ status: { state: "completed" }
105
+ }
106
+ };
107
+ console.log(`[PUSH] Sending push notification: ${pushText}`);
108
+ const response = await fetch(this.pushUrl, {
109
+ method: "POST",
110
+ headers: {
111
+ "Content-Type": "application/json",
112
+ "Accept": "application/json",
113
+ "x-hag-trace-id": this.generateUUID(),
114
+ "X-Access-Key": this.config.ak,
115
+ "X-Sign": signature,
116
+ "X-Ts": timestamp,
117
+ },
118
+ body: JSON.stringify(payload),
119
+ });
120
+ if (response.ok) {
121
+ console.log("[PUSH] Push notification sent successfully");
122
+ return true;
123
+ }
124
+ else {
125
+ console.error(`[PUSH] Failed: HTTP ${response.status}`);
126
+ return false;
127
+ }
128
+ }
129
+ catch (error) {
130
+ console.error("[PUSH] Error:", error);
131
+ return false;
132
+ }
133
+ }
134
+ }
135
+ exports.XiaoYiPushService = XiaoYiPushService;
package/dist/runtime.d.ts CHANGED
@@ -23,6 +23,11 @@ export declare class XiaoYiRuntime {
23
23
  private timeoutConfig;
24
24
  private sessionAbortControllerMap;
25
25
  private sessionActiveRunMap;
26
+ private sessionStartTimeMap;
27
+ private static readonly SESSION_STALE_TIMEOUT_MS;
28
+ private sessionTaskTimeoutMap;
29
+ private sessionPushPendingMap;
30
+ private taskTimeoutMs;
26
31
  constructor();
27
32
  getInstanceId(): string;
28
33
  /**
@@ -115,6 +120,7 @@ export declare class XiaoYiRuntime {
115
120
  } | null;
116
121
  /**
117
122
  * Check if a session has an active agent run
123
+ * If session is active but stale (超过 SESSION_STALE_TIMEOUT_MS), automatically clean up
118
124
  * @param sessionId - Session ID
119
125
  * @returns true if session is busy
120
126
  */
@@ -140,6 +146,46 @@ export declare class XiaoYiRuntime {
140
146
  * Clear all AbortControllers
141
147
  */
142
148
  clearAllAbortControllers(): void;
149
+ /**
150
+ * Generate a composite key for session+task combination
151
+ * This ensures each task has its own push state, even within the same session
152
+ */
153
+ private getPushStateKey;
154
+ /**
155
+ * Set task timeout time (from configuration)
156
+ */
157
+ setTaskTimeout(timeoutMs: number): void;
158
+ /**
159
+ * Set a 1-hour task timeout timer for a session
160
+ * @returns timeout ID
161
+ */
162
+ setTaskTimeoutForSession(sessionId: string, taskId: string, callback: (sessionId: string, taskId: string) => void): NodeJS.Timeout;
163
+ /**
164
+ * Clear the task timeout timer for a session
165
+ */
166
+ clearTaskTimeoutForSession(sessionId: string): void;
167
+ /**
168
+ * Check if session+task is waiting for push notification
169
+ * @param sessionId - Session ID
170
+ * @param taskId - Task ID (optional, for per-task tracking)
171
+ */
172
+ isSessionWaitingForPush(sessionId: string, taskId?: string): boolean;
173
+ /**
174
+ * Mark session+task as waiting for push notification
175
+ * @param sessionId - Session ID
176
+ * @param taskId - Task ID (optional, for per-task tracking)
177
+ */
178
+ markSessionWaitingForPush(sessionId: string, taskId?: string): void;
179
+ /**
180
+ * Clear the waiting push state for a session+task
181
+ * @param sessionId - Session ID
182
+ * @param taskId - Task ID (optional, for per-task tracking)
183
+ */
184
+ clearSessionWaitingForPush(sessionId: string, taskId?: string): void;
185
+ /**
186
+ * Clear all task timeout related state for a session
187
+ */
188
+ clearTaskTimeoutState(sessionId: string): void;
143
189
  }
144
190
  export declare function getXiaoYiRuntime(): XiaoYiRuntime;
145
191
  export declare function setXiaoYiRuntime(runtime: any): void;
package/dist/runtime.js CHANGED
@@ -30,6 +30,12 @@ class XiaoYiRuntime {
30
30
  this.sessionAbortControllerMap = new Map();
31
31
  // Track if a session has an active agent run (for concurrent request detection)
32
32
  this.sessionActiveRunMap = new Map();
33
+ // Track session start time for timeout detection
34
+ this.sessionStartTimeMap = new Map();
35
+ // 1-hour task timeout mechanism
36
+ this.sessionTaskTimeoutMap = new Map();
37
+ this.sessionPushPendingMap = new Map();
38
+ this.taskTimeoutMs = 3600000; // Default 1 hour
33
39
  this.instanceId = `runtime_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
34
40
  console.log(`XiaoYi: Created new runtime instance: ${this.instanceId}`);
35
41
  }
@@ -106,6 +112,10 @@ class XiaoYiRuntime {
106
112
  this.clearAllTimeouts();
107
113
  // Clear all abort controllers
108
114
  this.clearAllAbortControllers();
115
+ // Clear all task timeout state
116
+ for (const sessionId of this.sessionTaskTimeoutMap.keys()) {
117
+ this.clearTaskTimeoutState(sessionId);
118
+ }
109
119
  }
110
120
  /**
111
121
  * Set timeout configuration
@@ -246,16 +256,35 @@ class XiaoYiRuntime {
246
256
  const controller = new AbortController();
247
257
  this.sessionAbortControllerMap.set(sessionId, controller);
248
258
  this.sessionActiveRunMap.set(sessionId, true);
259
+ this.sessionStartTimeMap.set(sessionId, Date.now());
249
260
  console.log(`[ABORT] Created AbortController for session ${sessionId}`);
250
261
  return { controller, signal: controller.signal };
251
262
  }
252
263
  /**
253
264
  * Check if a session has an active agent run
265
+ * If session is active but stale (超过 SESSION_STALE_TIMEOUT_MS), automatically clean up
254
266
  * @param sessionId - Session ID
255
267
  * @returns true if session is busy
256
268
  */
257
269
  isSessionActive(sessionId) {
258
- return this.sessionActiveRunMap.get(sessionId) || false;
270
+ const isActive = this.sessionActiveRunMap.get(sessionId) || false;
271
+ if (isActive) {
272
+ // Check if the session has been active for too long
273
+ const startTime = this.sessionStartTimeMap.get(sessionId);
274
+ if (startTime) {
275
+ const elapsed = Date.now() - startTime;
276
+ if (elapsed > XiaoYiRuntime.SESSION_STALE_TIMEOUT_MS) {
277
+ // Session is stale, auto-cleanup and return false
278
+ console.log(`[CONCURRENT] Session ${sessionId} is stale (active for ${elapsed}ms), auto-cleaning`);
279
+ this.clearAbortControllerForSession(sessionId);
280
+ this.clearTaskIdForSession(sessionId);
281
+ this.clearSessionTimeout(sessionId);
282
+ this.sessionStartTimeMap.delete(sessionId);
283
+ return false;
284
+ }
285
+ }
286
+ }
287
+ return isActive;
259
288
  }
260
289
  /**
261
290
  * Abort a session's agent run
@@ -294,6 +323,8 @@ class XiaoYiRuntime {
294
323
  }
295
324
  // Also clear the active run flag
296
325
  this.sessionActiveRunMap.delete(sessionId);
326
+ // Clear the session start time
327
+ this.sessionStartTimeMap.delete(sessionId);
297
328
  console.log(`[CONCURRENT] Session ${sessionId} marked as inactive`);
298
329
  }
299
330
  /**
@@ -303,8 +334,91 @@ class XiaoYiRuntime {
303
334
  this.sessionAbortControllerMap.clear();
304
335
  console.log("[ABORT] All AbortControllers cleared");
305
336
  }
337
+ // ==================== PUSH STATE MANAGEMENT HELPERS ====================
338
+ /**
339
+ * Generate a composite key for session+task combination
340
+ * This ensures each task has its own push state, even within the same session
341
+ */
342
+ getPushStateKey(sessionId, taskId) {
343
+ return `${sessionId}:${taskId}`;
344
+ }
345
+ // ==================== END PUSH STATE MANAGEMENT HELPERS ====================
346
+ // ==================== 1-HOUR TASK TIMEOUT METHODS ====================
347
+ /**
348
+ * Set task timeout time (from configuration)
349
+ */
350
+ setTaskTimeout(timeoutMs) {
351
+ this.taskTimeoutMs = timeoutMs;
352
+ console.log(`[TASK TIMEOUT] Task timeout set to ${timeoutMs}ms`);
353
+ }
354
+ /**
355
+ * Set a 1-hour task timeout timer for a session
356
+ * @returns timeout ID
357
+ */
358
+ setTaskTimeoutForSession(sessionId, taskId, callback) {
359
+ this.clearTaskTimeoutForSession(sessionId);
360
+ const timeoutId = setTimeout(() => {
361
+ console.log(`[TASK TIMEOUT] ${this.taskTimeoutMs}ms timeout triggered for session ${sessionId}, task ${taskId}`);
362
+ callback(sessionId, taskId);
363
+ }, this.taskTimeoutMs);
364
+ this.sessionTaskTimeoutMap.set(sessionId, timeoutId);
365
+ console.log(`[TASK TIMEOUT] ${this.taskTimeoutMs}ms task timeout started for session ${sessionId}`);
366
+ return timeoutId;
367
+ }
368
+ /**
369
+ * Clear the task timeout timer for a session
370
+ */
371
+ clearTaskTimeoutForSession(sessionId) {
372
+ const timeoutId = this.sessionTaskTimeoutMap.get(sessionId);
373
+ if (timeoutId) {
374
+ clearTimeout(timeoutId);
375
+ this.sessionTaskTimeoutMap.delete(sessionId);
376
+ console.log(`[TASK TIMEOUT] Timeout cleared for session ${sessionId}`);
377
+ }
378
+ }
379
+ /**
380
+ * Check if session+task is waiting for push notification
381
+ * @param sessionId - Session ID
382
+ * @param taskId - Task ID (optional, for per-task tracking)
383
+ */
384
+ isSessionWaitingForPush(sessionId, taskId) {
385
+ const key = taskId ? this.getPushStateKey(sessionId, taskId) : sessionId;
386
+ return this.sessionPushPendingMap.get(key) === true;
387
+ }
388
+ /**
389
+ * Mark session+task as waiting for push notification
390
+ * @param sessionId - Session ID
391
+ * @param taskId - Task ID (optional, for per-task tracking)
392
+ */
393
+ markSessionWaitingForPush(sessionId, taskId) {
394
+ const key = taskId ? this.getPushStateKey(sessionId, taskId) : sessionId;
395
+ this.sessionPushPendingMap.set(key, true);
396
+ const taskInfo = taskId ? `, task ${taskId}` : '';
397
+ console.log(`[PUSH] Session ${sessionId}${taskInfo} marked as waiting for push`);
398
+ }
399
+ /**
400
+ * Clear the waiting push state for a session+task
401
+ * @param sessionId - Session ID
402
+ * @param taskId - Task ID (optional, for per-task tracking)
403
+ */
404
+ clearSessionWaitingForPush(sessionId, taskId) {
405
+ const key = taskId ? this.getPushStateKey(sessionId, taskId) : sessionId;
406
+ this.sessionPushPendingMap.delete(key);
407
+ const taskInfo = taskId ? `, task ${taskId}` : '';
408
+ console.log(`[PUSH] Session ${sessionId}${taskInfo} cleared from waiting for push`);
409
+ }
410
+ /**
411
+ * Clear all task timeout related state for a session
412
+ */
413
+ clearTaskTimeoutState(sessionId) {
414
+ this.clearTaskTimeoutForSession(sessionId);
415
+ this.clearSessionWaitingForPush(sessionId);
416
+ console.log(`[TASK TIMEOUT] All timeout state cleared for session ${sessionId}`);
417
+ }
306
418
  }
307
419
  exports.XiaoYiRuntime = XiaoYiRuntime;
420
+ // Maximum time a session can be active before we consider it stale (5 minutes)
421
+ XiaoYiRuntime.SESSION_STALE_TIMEOUT_MS = 5 * 60 * 1000;
308
422
  // Global runtime instance - use global object to survive module reloads
309
423
  // CRITICAL: Use string key instead of Symbol to ensure consistency across module reloads
310
424
  const GLOBAL_KEY = '__xiaoyi_runtime_instance__';
package/dist/types.d.ts CHANGED
@@ -152,6 +152,15 @@ export interface XiaoYiChannelConfig {
152
152
  sk: string;
153
153
  agentId: string;
154
154
  enableStreaming?: boolean;
155
+ apiId?: string;
156
+ pushId?: string;
157
+ taskTimeoutMs?: number;
158
+ /**
159
+ * Session cleanup timeout in milliseconds
160
+ * When user clears context, old sessions are cleaned up after this timeout
161
+ * Default: 1 hour (60 * 60 * 1000)
162
+ */
163
+ sessionCleanupTimeoutMs?: number;
155
164
  }
156
165
  export interface AuthCredentials {
157
166
  ak: string;
@@ -176,6 +185,7 @@ export interface InternalWebSocketConfig {
176
185
  ak: string;
177
186
  sk: string;
178
187
  enableStreaming?: boolean;
188
+ sessionCleanupTimeoutMs?: number;
179
189
  }
180
190
  export type ServerId = 'server1' | 'server2';
181
191
  export interface ServerConnectionState {
@@ -184,3 +194,14 @@ export interface ServerConnectionState {
184
194
  lastHeartbeat: number;
185
195
  reconnectAttempts: number;
186
196
  }
197
+ /**
198
+ * Session cleanup state for delayed cleanup
199
+ */
200
+ export interface SessionCleanupState {
201
+ sessionId: string;
202
+ serverId: ServerId;
203
+ markedForCleanupAt: number;
204
+ cleanupTimeoutId?: NodeJS.Timeout;
205
+ reason: 'user_cleared' | 'timeout' | 'error';
206
+ accumulatedText?: string;
207
+ }
@@ -1,11 +1,13 @@
1
1
  import { EventEmitter } from "events";
2
- import { A2AResponseMessage, WebSocketConnectionState, XiaoYiChannelConfig, ServerId, ServerConnectionState } from "./types";
2
+ import { A2AResponseMessage, WebSocketConnectionState, XiaoYiChannelConfig, ServerId, ServerConnectionState, SessionCleanupState } from "./types";
3
3
  export declare class XiaoYiWebSocketManager extends EventEmitter {
4
4
  private ws1;
5
5
  private ws2;
6
6
  private state1;
7
7
  private state2;
8
8
  private sessionServerMap;
9
+ private sessionCleanupStateMap;
10
+ private static readonly DEFAULT_CLEANUP_TIMEOUT_MS;
9
11
  private auth;
10
12
  private config;
11
13
  private heartbeatTimeout1?;
@@ -176,4 +178,34 @@ export declare class XiaoYiWebSocketManager extends EventEmitter {
176
178
  * Remove session mapping
177
179
  */
178
180
  removeSession(sessionId: string): void;
181
+ /**
182
+ * Mark a session for delayed cleanup
183
+ * @param sessionId The session ID to mark for cleanup
184
+ * @param serverId The server ID associated with this session
185
+ * @param timeoutMs Timeout in milliseconds before forcing cleanup
186
+ */
187
+ private markSessionForCleanup;
188
+ /**
189
+ * Force cleanup a session immediately
190
+ * @param sessionId The session ID to cleanup
191
+ */
192
+ forceCleanupSession(sessionId: string): void;
193
+ /**
194
+ * Check if a session is pending cleanup
195
+ * @param sessionId The session ID to check
196
+ * @returns True if session is pending cleanup
197
+ */
198
+ isSessionPendingCleanup(sessionId: string): boolean;
199
+ /**
200
+ * Get cleanup state for a session
201
+ * @param sessionId The session ID to check
202
+ * @returns Cleanup state if exists, undefined otherwise
203
+ */
204
+ getSessionCleanupState(sessionId: string): SessionCleanupState | undefined;
205
+ /**
206
+ * Update accumulated text for a pending cleanup session
207
+ * @param sessionId The session ID
208
+ * @param text The accumulated text
209
+ */
210
+ updateAccumulatedTextForCleanup(sessionId: string, text: string): void;
179
211
  }