@ynhcj/xiaoyi 2.5.5 → 2.5.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.
Files changed (43) hide show
  1. package/dist/auth.d.ts +1 -1
  2. package/dist/channel.d.ts +116 -14
  3. package/dist/channel.js +199 -665
  4. package/dist/config-schema.d.ts +8 -8
  5. package/dist/config-schema.js +5 -5
  6. package/dist/file-download.d.ts +17 -0
  7. package/dist/file-download.js +69 -0
  8. package/dist/heartbeat.d.ts +39 -0
  9. package/dist/heartbeat.js +102 -0
  10. package/dist/index.d.ts +1 -4
  11. package/dist/index.js +7 -11
  12. package/dist/push.d.ts +28 -0
  13. package/dist/push.js +135 -0
  14. package/dist/runtime.d.ts +48 -2
  15. package/dist/runtime.js +117 -3
  16. package/dist/types.d.ts +95 -1
  17. package/dist/websocket.d.ts +49 -1
  18. package/dist/websocket.js +279 -20
  19. package/dist/xy-bot.d.ts +19 -0
  20. package/dist/xy-bot.js +277 -0
  21. package/dist/xy-client.d.ts +26 -0
  22. package/dist/xy-client.js +78 -0
  23. package/dist/xy-config.d.ts +18 -0
  24. package/dist/xy-config.js +37 -0
  25. package/dist/xy-formatter.d.ts +94 -0
  26. package/dist/xy-formatter.js +303 -0
  27. package/dist/xy-monitor.d.ts +17 -0
  28. package/dist/xy-monitor.js +187 -0
  29. package/dist/xy-parser.d.ts +49 -0
  30. package/dist/xy-parser.js +109 -0
  31. package/dist/xy-reply-dispatcher.d.ts +17 -0
  32. package/dist/xy-reply-dispatcher.js +308 -0
  33. package/dist/xy-tools/session-manager.d.ts +29 -0
  34. package/dist/xy-tools/session-manager.js +80 -0
  35. package/dist/xy-utils/config-manager.d.ts +26 -0
  36. package/dist/xy-utils/config-manager.js +61 -0
  37. package/dist/xy-utils/crypto.d.ts +8 -0
  38. package/dist/xy-utils/crypto.js +21 -0
  39. package/dist/xy-utils/logger.d.ts +6 -0
  40. package/dist/xy-utils/logger.js +37 -0
  41. package/dist/xy-utils/session.d.ts +34 -0
  42. package/dist/xy-utils/session.js +55 -0
  43. package/package.json +32 -16
package/dist/runtime.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.XiaoYiRuntime = void 0;
4
4
  exports.getXiaoYiRuntime = getXiaoYiRuntime;
5
5
  exports.setXiaoYiRuntime = setXiaoYiRuntime;
6
- const websocket_1 = require("./websocket");
6
+ const websocket_js_1 = require("./websocket.js");
7
7
  /**
8
8
  * Default timeout configuration
9
9
  */
@@ -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
  }
@@ -58,7 +64,7 @@ class XiaoYiRuntime {
58
64
  return;
59
65
  }
60
66
  this.config = config;
61
- const manager = new websocket_1.XiaoYiWebSocketManager(config);
67
+ const manager = new websocket_js_1.XiaoYiWebSocketManager(config);
62
68
  // Setup basic event handlers (message handling is done in channel.ts)
63
69
  manager.on("error", (error) => {
64
70
  console.error("XiaoYi channel error:", error);
@@ -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
@@ -1,3 +1,69 @@
1
+ export interface A2AJsonRpcRequest {
2
+ jsonrpc: "2.0";
3
+ method: string;
4
+ params: A2ARequestParams;
5
+ id: string;
6
+ }
7
+ export interface A2ARequestParams {
8
+ id: string;
9
+ sessionId: string;
10
+ agentLoginSessionId?: string;
11
+ message: A2AMessage;
12
+ }
13
+ export interface A2AMessage {
14
+ role: "user" | "assistant" | "system";
15
+ parts: A2AMessagePart[];
16
+ }
17
+ export type A2AMessagePart = A2ATextPart | A2AFilePart | A2ADataPart;
18
+ export interface A2ATextPart {
19
+ kind: "text";
20
+ text: string;
21
+ }
22
+ export interface A2AFilePart {
23
+ kind: "file";
24
+ file: {
25
+ name: string;
26
+ mimeType: string;
27
+ uri: string;
28
+ };
29
+ }
30
+ export interface A2ADataPart {
31
+ kind: "data";
32
+ data: {
33
+ event?: A2ADataEvent;
34
+ variables?: {
35
+ systemVariables?: {
36
+ push_id?: string;
37
+ };
38
+ };
39
+ [key: string]: any;
40
+ };
41
+ }
42
+ export interface A2ADataEvent {
43
+ intentName: string;
44
+ outputs: Record<string, any>;
45
+ status: "success" | "failed";
46
+ }
47
+ export interface A2AReasoningTextPart {
48
+ kind: "reasoningText";
49
+ reasoningText: string;
50
+ }
51
+ export interface A2ACommandPart {
52
+ kind: "command";
53
+ command: A2ACommand;
54
+ }
55
+ export interface A2ACommand {
56
+ header: {
57
+ namespace: string;
58
+ name: string;
59
+ };
60
+ payload: Record<string, any>;
61
+ }
62
+ export type A2AArtifactPart = A2ATextPart | A2ADataPart | A2ACommandPart | A2AReasoningTextPart;
63
+ export interface A2AArtifact {
64
+ artifactId: string;
65
+ parts: A2AArtifactPart[];
66
+ }
1
67
  export interface A2ARequestMessage {
2
68
  agentId: string;
3
69
  jsonrpc: "2.0";
@@ -75,8 +141,9 @@ export interface A2ATaskArtifactUpdateEvent {
75
141
  artifact: {
76
142
  artifactId: string;
77
143
  parts: Array<{
78
- kind: "text" | "file" | "data";
144
+ kind: "text" | "file" | "data" | "reasoningText";
79
145
  text?: string;
146
+ reasoningText?: string;
80
147
  file?: {
81
148
  name: string;
82
149
  mimeType: string;
@@ -152,6 +219,15 @@ export interface XiaoYiChannelConfig {
152
219
  sk: string;
153
220
  agentId: string;
154
221
  enableStreaming?: boolean;
222
+ apiId?: string;
223
+ pushId?: string;
224
+ taskTimeoutMs?: number;
225
+ /**
226
+ * Session cleanup timeout in milliseconds
227
+ * When user clears context, old sessions are cleaned up after this timeout
228
+ * Default: 1 hour (60 * 60 * 1000)
229
+ */
230
+ sessionCleanupTimeoutMs?: number;
155
231
  }
156
232
  export interface AuthCredentials {
157
233
  ak: string;
@@ -176,6 +252,7 @@ export interface InternalWebSocketConfig {
176
252
  ak: string;
177
253
  sk: string;
178
254
  enableStreaming?: boolean;
255
+ sessionCleanupTimeoutMs?: number;
179
256
  }
180
257
  export type ServerId = 'server1' | 'server2';
181
258
  export interface ServerConnectionState {
@@ -184,3 +261,20 @@ export interface ServerConnectionState {
184
261
  lastHeartbeat: number;
185
262
  reconnectAttempts: number;
186
263
  }
264
+ /**
265
+ * Session cleanup state for delayed cleanup
266
+ */
267
+ export interface SessionCleanupState {
268
+ sessionId: string;
269
+ serverId: ServerId;
270
+ markedForCleanupAt: number;
271
+ cleanupTimeoutId?: NodeJS.Timeout;
272
+ reason: 'user_cleared' | 'timeout' | 'error';
273
+ accumulatedText?: string;
274
+ }
275
+ export interface SessionBinding {
276
+ sessionId: string;
277
+ server: ServerIdentifier;
278
+ boundAt: number;
279
+ }
280
+ export type ServerIdentifier = 'server1' | 'server2';
@@ -1,23 +1,33 @@
1
1
  import { EventEmitter } from "events";
2
- import { A2AResponseMessage, WebSocketConnectionState, XiaoYiChannelConfig, ServerId, ServerConnectionState } from "./types";
2
+ import { A2AResponseMessage, OutboundWebSocketMessage, WebSocketConnectionState, XiaoYiChannelConfig, ServerId, ServerConnectionState, SessionCleanupState } from "./types.js";
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?;
12
14
  private heartbeatTimeout2?;
13
15
  private appHeartbeatInterval?;
16
+ private heartbeat1?;
17
+ private heartbeat2?;
14
18
  private reconnectTimeout1?;
15
19
  private reconnectTimeout2?;
16
20
  private stableConnectionTimer1?;
17
21
  private stableConnectionTimer2?;
18
22
  private static readonly STABLE_CONNECTION_THRESHOLD;
19
23
  private activeTasks;
24
+ private onHealthEvent?;
20
25
  constructor(config: XiaoYiChannelConfig);
26
+ /**
27
+ * Set health event callback to report activity to OpenClaw framework.
28
+ * This callback is invoked on heartbeat success to update lastEventAt.
29
+ */
30
+ setHealthEventCallback(callback: () => void): void;
21
31
  /**
22
32
  * Check if URL is wss + IP format (skip certificate verification)
23
33
  */
@@ -91,6 +101,14 @@ export declare class XiaoYiWebSocketManager extends EventEmitter {
91
101
  * TODO: 实现实际的推送消息发送逻辑
92
102
  */
93
103
  sendPushMessage(sessionId: string, message: string): Promise<void>;
104
+ /**
105
+ * Send an outbound WebSocket message directly.
106
+ * This is a low-level method that sends a pre-formatted OutboundWebSocketMessage.
107
+ *
108
+ * @param sessionId - Session ID for routing
109
+ * @param message - Pre-formatted outbound message
110
+ */
111
+ sendMessage(sessionId: string, message: OutboundWebSocketMessage): Promise<void>;
94
112
  /**
95
113
  * Send tasks cancel response to specific server
96
114
  */
@@ -176,4 +194,34 @@ export declare class XiaoYiWebSocketManager extends EventEmitter {
176
194
  * Remove session mapping
177
195
  */
178
196
  removeSession(sessionId: string): void;
197
+ /**
198
+ * Mark a session for delayed cleanup
199
+ * @param sessionId The session ID to mark for cleanup
200
+ * @param serverId The server ID associated with this session
201
+ * @param timeoutMs Timeout in milliseconds before forcing cleanup
202
+ */
203
+ private markSessionForCleanup;
204
+ /**
205
+ * Force cleanup a session immediately
206
+ * @param sessionId The session ID to cleanup
207
+ */
208
+ forceCleanupSession(sessionId: string): void;
209
+ /**
210
+ * Check if a session is pending cleanup
211
+ * @param sessionId The session ID to check
212
+ * @returns True if session is pending cleanup
213
+ */
214
+ isSessionPendingCleanup(sessionId: string): boolean;
215
+ /**
216
+ * Get cleanup state for a session
217
+ * @param sessionId The session ID to check
218
+ * @returns Cleanup state if exists, undefined otherwise
219
+ */
220
+ getSessionCleanupState(sessionId: string): SessionCleanupState | undefined;
221
+ /**
222
+ * Update accumulated text for a pending cleanup session
223
+ * @param sessionId The session ID
224
+ * @param text The accumulated text
225
+ */
226
+ updateAccumulatedTextForCleanup(sessionId: string, text: string): void;
179
227
  }