@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.
- package/README.md +207 -0
- package/dist/auth.d.ts +36 -0
- package/dist/auth.js +111 -0
- package/dist/channel.d.ts +189 -0
- package/dist/channel.js +354 -0
- package/dist/config-schema.d.ts +46 -0
- package/dist/config-schema.js +28 -0
- package/dist/file-download.d.ts +17 -0
- package/dist/file-download.js +69 -0
- package/dist/file-handler.d.ts +36 -0
- package/dist/file-handler.js +113 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +49 -0
- package/dist/onboarding.d.ts +6 -0
- package/dist/onboarding.js +167 -0
- package/dist/push.d.ts +28 -0
- package/dist/push.js +135 -0
- package/dist/runtime.d.ts +191 -0
- package/dist/runtime.js +438 -0
- package/dist/types.d.ts +280 -0
- package/dist/types.js +8 -0
- package/dist/websocket.d.ts +219 -0
- package/dist/websocket.js +1068 -0
- package/dist/xiaoyi-media.d.ts +81 -0
- package/dist/xiaoyi-media.js +216 -0
- package/dist/xy-bot.d.ts +19 -0
- package/dist/xy-bot.js +277 -0
- package/dist/xy-client.d.ts +26 -0
- package/dist/xy-client.js +78 -0
- package/dist/xy-config.d.ts +18 -0
- package/dist/xy-config.js +37 -0
- package/dist/xy-formatter.d.ts +94 -0
- package/dist/xy-formatter.js +303 -0
- package/dist/xy-monitor.d.ts +17 -0
- package/dist/xy-monitor.js +194 -0
- package/dist/xy-parser.d.ts +49 -0
- package/dist/xy-parser.js +109 -0
- package/dist/xy-reply-dispatcher.d.ts +17 -0
- package/dist/xy-reply-dispatcher.js +308 -0
- package/dist/xy-tools/session-manager.d.ts +29 -0
- package/dist/xy-tools/session-manager.js +80 -0
- package/dist/xy-utils/config-manager.d.ts +26 -0
- package/dist/xy-utils/config-manager.js +61 -0
- package/dist/xy-utils/crypto.d.ts +8 -0
- package/dist/xy-utils/crypto.js +21 -0
- package/dist/xy-utils/logger.d.ts +6 -0
- package/dist/xy-utils/logger.js +37 -0
- package/dist/xy-utils/session.d.ts +34 -0
- package/dist/xy-utils/session.js +55 -0
- package/openclaw.plugin.json +9 -0
- package/package.json +73 -0
- package/xiaoyi.js +1 -0
package/dist/runtime.js
ADDED
|
@@ -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
|
+
}
|