@ynhcj/xiaoyi 0.0.1-beta

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 (52) hide show
  1. package/README.md +207 -0
  2. package/dist/auth.d.ts +36 -0
  3. package/dist/auth.js +111 -0
  4. package/dist/channel.d.ts +189 -0
  5. package/dist/channel.js +354 -0
  6. package/dist/config-schema.d.ts +46 -0
  7. package/dist/config-schema.js +28 -0
  8. package/dist/file-download.d.ts +17 -0
  9. package/dist/file-download.js +69 -0
  10. package/dist/file-handler.d.ts +36 -0
  11. package/dist/file-handler.js +113 -0
  12. package/dist/index.d.ts +29 -0
  13. package/dist/index.js +49 -0
  14. package/dist/onboarding.d.ts +6 -0
  15. package/dist/onboarding.js +167 -0
  16. package/dist/push.d.ts +28 -0
  17. package/dist/push.js +135 -0
  18. package/dist/runtime.d.ts +191 -0
  19. package/dist/runtime.js +438 -0
  20. package/dist/types.d.ts +280 -0
  21. package/dist/types.js +8 -0
  22. package/dist/websocket.d.ts +219 -0
  23. package/dist/websocket.js +1068 -0
  24. package/dist/xiaoyi-media.d.ts +81 -0
  25. package/dist/xiaoyi-media.js +216 -0
  26. package/dist/xy-bot.d.ts +19 -0
  27. package/dist/xy-bot.js +277 -0
  28. package/dist/xy-client.d.ts +26 -0
  29. package/dist/xy-client.js +78 -0
  30. package/dist/xy-config.d.ts +18 -0
  31. package/dist/xy-config.js +37 -0
  32. package/dist/xy-formatter.d.ts +94 -0
  33. package/dist/xy-formatter.js +303 -0
  34. package/dist/xy-monitor.d.ts +17 -0
  35. package/dist/xy-monitor.js +194 -0
  36. package/dist/xy-parser.d.ts +49 -0
  37. package/dist/xy-parser.js +109 -0
  38. package/dist/xy-reply-dispatcher.d.ts +17 -0
  39. package/dist/xy-reply-dispatcher.js +308 -0
  40. package/dist/xy-tools/session-manager.d.ts +29 -0
  41. package/dist/xy-tools/session-manager.js +80 -0
  42. package/dist/xy-utils/config-manager.d.ts +26 -0
  43. package/dist/xy-utils/config-manager.js +61 -0
  44. package/dist/xy-utils/crypto.d.ts +8 -0
  45. package/dist/xy-utils/crypto.js +21 -0
  46. package/dist/xy-utils/logger.d.ts +6 -0
  47. package/dist/xy-utils/logger.js +37 -0
  48. package/dist/xy-utils/session.d.ts +34 -0
  49. package/dist/xy-utils/session.js +55 -0
  50. package/openclaw.plugin.json +9 -0
  51. package/package.json +73 -0
  52. package/xiaoyi.js +1 -0
@@ -0,0 +1,438 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.XiaoYiRuntime = void 0;
4
+ exports.getXiaoYiRuntime = getXiaoYiRuntime;
5
+ exports.setXiaoYiRuntime = setXiaoYiRuntime;
6
+ const websocket_js_1 = require("./websocket.js");
7
+ /**
8
+ * Default timeout configuration
9
+ */
10
+ const DEFAULT_TIMEOUT_CONFIG = {
11
+ enabled: true,
12
+ duration: 60000, // 60 seconds
13
+ message: "任务正在处理中,请稍后",
14
+ };
15
+ /**
16
+ * Runtime state for XiaoYi channel
17
+ * Manages single WebSocket connection (single account mode)
18
+ */
19
+ class XiaoYiRuntime {
20
+ constructor() {
21
+ this.connection = null;
22
+ this.pluginRuntime = null; // Store PluginRuntime from OpenClaw
23
+ this.config = null;
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;
29
+ // AbortController management for canceling agent runs
30
+ this.sessionAbortControllerMap = new Map();
31
+ // Track if a session has an active agent run (for concurrent request detection)
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
39
+ this.instanceId = `runtime_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
40
+ console.log(`XiaoYi: Created new runtime instance: ${this.instanceId}`);
41
+ }
42
+ getInstanceId() {
43
+ return this.instanceId;
44
+ }
45
+ /**
46
+ * Set OpenClaw PluginRuntime (from api.runtime in register())
47
+ */
48
+ setPluginRuntime(runtime) {
49
+ console.log(`XiaoYi: [${this.instanceId}] Setting PluginRuntime`);
50
+ this.pluginRuntime = runtime;
51
+ }
52
+ /**
53
+ * Get OpenClaw PluginRuntime
54
+ */
55
+ getPluginRuntime() {
56
+ return this.pluginRuntime;
57
+ }
58
+ /**
59
+ * Start connection (single account mode)
60
+ */
61
+ async start(config) {
62
+ if (this.connection) {
63
+ console.log("XiaoYi channel already connected");
64
+ return;
65
+ }
66
+ this.config = config;
67
+ const manager = new websocket_js_1.XiaoYiWebSocketManager(config);
68
+ // Setup basic event handlers (message handling is done in channel.ts)
69
+ manager.on("error", (error) => {
70
+ console.error("XiaoYi channel error:", error);
71
+ });
72
+ manager.on("disconnected", () => {
73
+ console.log("XiaoYi channel disconnected");
74
+ });
75
+ manager.on("authenticated", () => {
76
+ console.log("XiaoYi channel authenticated");
77
+ });
78
+ manager.on("maxReconnectAttemptsReached", (serverId) => {
79
+ console.error(`XiaoYi channel ${serverId} max reconnect attempts reached`);
80
+ // Check if the other server is still connected and ready
81
+ const otherServerId = serverId === 'server1' ? 'server2' : 'server1';
82
+ const serverStates = manager.getServerStates();
83
+ const otherServerState = otherServerId === 'server1' ? serverStates.server1 : serverStates.server2;
84
+ if (otherServerState?.connected && otherServerState?.ready) {
85
+ console.warn(`[${otherServerId}] is still connected and ready, continuing in single-server mode`);
86
+ console.warn(`System will continue running with ${otherServerId} only`);
87
+ // Don't stop, continue with the other server
88
+ return;
89
+ }
90
+ // Only stop when both servers have failed
91
+ console.error("Both servers have reached max reconnect attempts, stopping connection");
92
+ console.error(`Server1: ${serverStates.server1.connected ? 'connected' : 'disconnected'}, Server2: ${serverStates.server2.connected ? 'connected' : 'disconnected'}`);
93
+ this.stop();
94
+ });
95
+ // Connect
96
+ await manager.connect();
97
+ this.connection = manager;
98
+ console.log("XiaoYi channel started");
99
+ }
100
+ /**
101
+ * Stop connection
102
+ */
103
+ stop() {
104
+ if (this.connection) {
105
+ this.connection.disconnect();
106
+ this.connection = null;
107
+ console.log("XiaoYi channel stopped");
108
+ }
109
+ // Clear session mappings
110
+ this.sessionToTaskIdMap.clear();
111
+ // Clear all timeouts
112
+ this.clearAllTimeouts();
113
+ // Clear all abort controllers
114
+ this.clearAllAbortControllers();
115
+ // Clear all task timeout state
116
+ for (const sessionId of this.sessionTaskTimeoutMap.keys()) {
117
+ this.clearTaskTimeoutState(sessionId);
118
+ }
119
+ }
120
+ /**
121
+ * Set timeout configuration
122
+ */
123
+ setTimeoutConfig(config) {
124
+ this.timeoutConfig = { ...this.timeoutConfig, ...config };
125
+ console.log(`XiaoYi: Timeout config updated:`, this.timeoutConfig);
126
+ }
127
+ /**
128
+ * Get timeout configuration
129
+ */
130
+ getTimeoutConfig() {
131
+ return { ...this.timeoutConfig };
132
+ }
133
+ /**
134
+ * Set timeout for a session
135
+ * @param sessionId - Session ID
136
+ * @param callback - Function to call when timeout occurs
137
+ * @returns The interval ID (for cancellation)
138
+ *
139
+ * IMPORTANT: This now uses setInterval instead of setTimeout
140
+ * - First trigger: after 60 seconds
141
+ * - Subsequent triggers: every 60 seconds after that
142
+ * - Cleared when: response received, session completed, or explicitly cleared
143
+ */
144
+ setTimeoutForSession(sessionId, callback) {
145
+ if (!this.timeoutConfig.enabled) {
146
+ console.log(`[TIMEOUT] Timeout disabled, skipping for session ${sessionId}`);
147
+ return undefined;
148
+ }
149
+ // Clear existing timeout AND timeout flag if any (reuse session scenario)
150
+ const hadExistingTimeout = this.sessionTimeoutMap.has(sessionId);
151
+ const hadSentTimeout = this.sessionTimeoutSent.has(sessionId);
152
+ this.clearSessionTimeout(sessionId);
153
+ // Clear the timeout sent flag to allow this session to timeout again
154
+ if (hadSentTimeout) {
155
+ this.sessionTimeoutSent.delete(sessionId);
156
+ console.log(`[TIMEOUT] Previous timeout flag cleared for session ${sessionId} (session reuse)`);
157
+ }
158
+ // Use setInterval for periodic timeout triggers
159
+ // First trigger after duration, then every duration after that
160
+ const intervalId = setInterval(() => {
161
+ console.log(`[TIMEOUT] Timeout triggered for session ${sessionId} (will trigger again in ${this.timeoutConfig.duration}ms if still active)`);
162
+ this.sessionTimeoutSent.add(sessionId);
163
+ callback();
164
+ }, this.timeoutConfig.duration);
165
+ this.sessionTimeoutMap.set(sessionId, intervalId);
166
+ const logSuffix = hadExistingTimeout ? " (replacing existing interval)" : "";
167
+ console.log(`[TIMEOUT] ${this.timeoutConfig.duration}ms periodic timeout started for session ${sessionId}${logSuffix}`);
168
+ return intervalId;
169
+ }
170
+ /**
171
+ * Clear timeout interval for a session
172
+ * @param sessionId - Session ID
173
+ */
174
+ clearSessionTimeout(sessionId) {
175
+ const intervalId = this.sessionTimeoutMap.get(sessionId);
176
+ if (intervalId) {
177
+ clearInterval(intervalId);
178
+ this.sessionTimeoutMap.delete(sessionId);
179
+ console.log(`[TIMEOUT] Timeout interval cleared for session ${sessionId}`);
180
+ }
181
+ }
182
+ /**
183
+ * Check if timeout has been sent for a session
184
+ * @param sessionId - Session ID
185
+ */
186
+ isSessionTimeout(sessionId) {
187
+ return this.sessionTimeoutSent.has(sessionId);
188
+ }
189
+ /**
190
+ * Mark session as completed (clear timeout and timeout flag)
191
+ * @param sessionId - Session ID
192
+ */
193
+ markSessionCompleted(sessionId) {
194
+ this.clearSessionTimeout(sessionId);
195
+ this.sessionTimeoutSent.delete(sessionId);
196
+ console.log(`[TIMEOUT] Session ${sessionId} marked as completed`);
197
+ }
198
+ /**
199
+ * Clear all timeout intervals
200
+ */
201
+ clearAllTimeouts() {
202
+ for (const [sessionId, intervalId] of this.sessionTimeoutMap.entries()) {
203
+ clearInterval(intervalId);
204
+ }
205
+ this.sessionTimeoutMap.clear();
206
+ this.sessionTimeoutSent.clear();
207
+ console.log("[TIMEOUT] All timeout intervals cleared");
208
+ }
209
+ /**
210
+ * Get WebSocket manager
211
+ */
212
+ getConnection() {
213
+ return this.connection;
214
+ }
215
+ /**
216
+ * Check if connected
217
+ */
218
+ isConnected() {
219
+ return this.connection ? this.connection.isReady() : false;
220
+ }
221
+ /**
222
+ * Get configuration
223
+ */
224
+ getConfig() {
225
+ return this.config;
226
+ }
227
+ /**
228
+ * Set taskId for a session
229
+ */
230
+ setTaskIdForSession(sessionId, taskId) {
231
+ this.sessionToTaskIdMap.set(sessionId, taskId);
232
+ }
233
+ /**
234
+ * Get taskId for a session
235
+ */
236
+ getTaskIdForSession(sessionId) {
237
+ return this.sessionToTaskIdMap.get(sessionId);
238
+ }
239
+ /**
240
+ * Clear taskId for a session
241
+ */
242
+ clearTaskIdForSession(sessionId) {
243
+ this.sessionToTaskIdMap.delete(sessionId);
244
+ }
245
+ /**
246
+ * Create and register an AbortController for a session
247
+ * @param sessionId - Session ID
248
+ * @returns The AbortController and its signal, or null if session is busy
249
+ */
250
+ createAbortControllerForSession(sessionId) {
251
+ // Check if there's an active agent run for this session
252
+ if (this.sessionActiveRunMap.get(sessionId)) {
253
+ console.log(`[CONCURRENT] Session ${sessionId} has an active agent run, cannot create new AbortController`);
254
+ return null;
255
+ }
256
+ const controller = new AbortController();
257
+ this.sessionAbortControllerMap.set(sessionId, controller);
258
+ this.sessionActiveRunMap.set(sessionId, true);
259
+ this.sessionStartTimeMap.set(sessionId, Date.now());
260
+ console.log(`[ABORT] Created AbortController for session ${sessionId}`);
261
+ return { controller, signal: controller.signal };
262
+ }
263
+ /**
264
+ * Check if a session has an active agent run
265
+ * If session is active but stale (超过 SESSION_STALE_TIMEOUT_MS), automatically clean up
266
+ * @param sessionId - Session ID
267
+ * @returns true if session is busy
268
+ */
269
+ isSessionActive(sessionId) {
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;
288
+ }
289
+ /**
290
+ * Abort a session's agent run
291
+ * @param sessionId - Session ID
292
+ * @returns true if a controller was found and aborted, false otherwise
293
+ */
294
+ abortSession(sessionId) {
295
+ const controller = this.sessionAbortControllerMap.get(sessionId);
296
+ if (controller) {
297
+ console.log(`[ABORT] Aborting session ${sessionId}`);
298
+ controller.abort();
299
+ this.sessionAbortControllerMap.delete(sessionId);
300
+ return true;
301
+ }
302
+ console.log(`[ABORT] No AbortController found for session ${sessionId}`);
303
+ return false;
304
+ }
305
+ /**
306
+ * Check if a session has been aborted
307
+ * @param sessionId - Session ID
308
+ * @returns true if the session's abort signal was triggered
309
+ */
310
+ isSessionAborted(sessionId) {
311
+ const controller = this.sessionAbortControllerMap.get(sessionId);
312
+ return controller ? controller.signal.aborted : false;
313
+ }
314
+ /**
315
+ * Clear the AbortController for a session (call when agent completes successfully)
316
+ * @param sessionId - Session ID
317
+ */
318
+ clearAbortControllerForSession(sessionId) {
319
+ const controller = this.sessionAbortControllerMap.get(sessionId);
320
+ if (controller) {
321
+ this.sessionAbortControllerMap.delete(sessionId);
322
+ console.log(`[ABORT] Cleared AbortController for session ${sessionId}`);
323
+ }
324
+ // Also clear the active run flag
325
+ this.sessionActiveRunMap.delete(sessionId);
326
+ // Clear the session start time
327
+ this.sessionStartTimeMap.delete(sessionId);
328
+ console.log(`[CONCURRENT] Session ${sessionId} marked as inactive`);
329
+ }
330
+ /**
331
+ * Clear all AbortControllers
332
+ */
333
+ clearAllAbortControllers() {
334
+ this.sessionAbortControllerMap.clear();
335
+ console.log("[ABORT] All AbortControllers cleared");
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
+ }
418
+ }
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;
422
+ // Global runtime instance - use global object to survive module reloads
423
+ // CRITICAL: Use string key instead of Symbol to ensure consistency across module reloads
424
+ const GLOBAL_KEY = '__xiaoyi_runtime_instance__';
425
+ function getXiaoYiRuntime() {
426
+ const g = global;
427
+ if (!g[GLOBAL_KEY]) {
428
+ console.log("XiaoYi: Creating NEW runtime instance (global storage)");
429
+ g[GLOBAL_KEY] = new XiaoYiRuntime();
430
+ }
431
+ else {
432
+ console.log(`XiaoYi: Reusing EXISTING runtime instance: ${g[GLOBAL_KEY].getInstanceId()}`);
433
+ }
434
+ return g[GLOBAL_KEY];
435
+ }
436
+ function setXiaoYiRuntime(runtime) {
437
+ getXiaoYiRuntime().setPluginRuntime(runtime);
438
+ }