@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
@@ -0,0 +1,308 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createXYReplyDispatcher = createXYReplyDispatcher;
4
+ const runtime_js_1 = require("./runtime.js");
5
+ const xy_formatter_js_1 = require("./xy-formatter.js");
6
+ const xy_config_js_1 = require("./xy-config.js");
7
+ /**
8
+ * Create a reply dispatcher for XY channel messages.
9
+ * Follows feishu pattern with status updates and streaming support.
10
+ * Runtime is expected to be validated before calling this function.
11
+ */
12
+ function createXYReplyDispatcher(params) {
13
+ const { cfg, runtime, sessionId, taskId, messageId, accountId } = params;
14
+ const log = runtime?.log ?? console.log;
15
+ const error = runtime?.error ?? console.error;
16
+ log(`[DISPATCHER-CREATE] ******* Creating dispatcher for session=${sessionId}, taskId=${taskId}, messageId=${messageId} *******`);
17
+ log(`[DISPATCHER-CREATE] Stack trace:`, new Error().stack?.split('\n').slice(1, 4).join('\n'));
18
+ log(`[DISPATCHER-CREATE] ======== Creating reply dispatcher ========`);
19
+ log(`[DISPATCHER-CREATE] sessionId: ${sessionId}, taskId: ${taskId}, messageId: ${messageId}`);
20
+ log(`[DISPATCHER-CREATE] Stack trace:`, new Error().stack?.split('\n').slice(1, 4).join('\n'));
21
+ // Get OpenClaw PluginRuntime (not XiaoYiRuntime)
22
+ const xiaoYiRuntime = (0, runtime_js_1.getXiaoYiRuntime)();
23
+ const core = xiaoYiRuntime.getPluginRuntime();
24
+ // Resolve configuration
25
+ const config = (0, xy_config_js_1.resolveXYConfig)(cfg);
26
+ // Reply prefix context: not imported at runtime to avoid openclaw/plugin-sdk
27
+ // module resolution issues in the CJS require chain. For a bot-to-bot A2A
28
+ // channel the response prefix (model name badge) is not needed.
29
+ const prefixContext = { responsePrefix: undefined, responsePrefixContextProvider: undefined, onModelSelected: undefined };
30
+ // Status update interval (every 60 seconds)
31
+ let statusUpdateInterval = null;
32
+ // Track if we've sent any response
33
+ let hasSentResponse = false;
34
+ // Track if we've sent the final empty message
35
+ let finalSent = false;
36
+ // Accumulate all text from deliver calls
37
+ let accumulatedText = "";
38
+ /**
39
+ * Start the status update interval
40
+ * Call this immediately after creating the dispatcher
41
+ */
42
+ const startStatusInterval = () => {
43
+ log(`[STATUS INTERVAL] Starting interval for session ${sessionId}, taskId=${taskId}`);
44
+ statusUpdateInterval = setInterval(() => {
45
+ log(`[STATUS INTERVAL] Triggering status update for session ${sessionId}, taskId=${taskId}`);
46
+ void (0, xy_formatter_js_1.sendStatusUpdate)({
47
+ config,
48
+ sessionId,
49
+ taskId,
50
+ messageId,
51
+ text: "任务正在处理中,请稍后~",
52
+ state: "working",
53
+ }).catch((err) => {
54
+ error(`Failed to send status update:`, err);
55
+ });
56
+ }, 30000); // 30 seconds
57
+ };
58
+ /**
59
+ * Stop the status update interval
60
+ */
61
+ const stopStatusInterval = () => {
62
+ if (statusUpdateInterval) {
63
+ log(`[STATUS INTERVAL] Stopping interval for session ${sessionId}, taskId=${taskId}`);
64
+ clearInterval(statusUpdateInterval);
65
+ statusUpdateInterval = null;
66
+ log(`[STATUS INTERVAL] Stopped interval for session ${sessionId}, taskId=${taskId}`);
67
+ }
68
+ };
69
+ const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
70
+ responsePrefix: prefixContext.responsePrefix,
71
+ responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
72
+ humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, accountId),
73
+ onReplyStart: () => {
74
+ log(`[REPLY START] Reply started for session ${sessionId}, taskId=${taskId}`);
75
+ // Status update interval is now managed externally
76
+ },
77
+ deliver: async (payload, info) => {
78
+ const text = payload.text ?? "";
79
+ // 🔍 Debug logging
80
+ log(`[DELIVER] sessionId=${sessionId}, info.kind=${info?.kind}, text.length=${text.length}, text="${text.slice(0, 200)}"`);
81
+ log(`[DELIVER] payload keys: ${Object.keys(payload).join(", ")}`);
82
+ if (payload.mediaUrls) {
83
+ log(`[DELIVER] mediaUrls: ${payload.mediaUrls.length} files`);
84
+ }
85
+ try {
86
+ // Skip empty messages
87
+ if (!text.trim()) {
88
+ log(`[DELIVER SKIP] Empty text, skipping`);
89
+ return;
90
+ }
91
+ // Accumulate text instead of sending immediately
92
+ accumulatedText += text;
93
+ hasSentResponse = true;
94
+ log(`[DELIVER ACCUMULATE] Accumulated text, current length=${accumulatedText.length}`);
95
+ // Also stream text as reasoningText for real-time display
96
+ await (0, xy_formatter_js_1.sendReasoningTextUpdate)({
97
+ config,
98
+ sessionId,
99
+ taskId,
100
+ messageId,
101
+ text,
102
+ });
103
+ log(`[DELIVER] ✅ Sent deliver text as reasoningText update`);
104
+ }
105
+ catch (deliverError) {
106
+ error(`Failed to deliver message:`, deliverError);
107
+ }
108
+ },
109
+ onError: async (err, info) => {
110
+ runtime.error?.(`xy: ${info.kind} reply failed: ${String(err)}`);
111
+ // Stop status updates
112
+ stopStatusInterval();
113
+ // Send error status if we haven't sent any response yet
114
+ if (!hasSentResponse) {
115
+ try {
116
+ await (0, xy_formatter_js_1.sendStatusUpdate)({
117
+ config,
118
+ sessionId,
119
+ taskId,
120
+ messageId,
121
+ text: "处理失败,请稍后重试",
122
+ state: "failed",
123
+ });
124
+ }
125
+ catch (statusError) {
126
+ error(`Failed to send error status:`, statusError);
127
+ }
128
+ }
129
+ },
130
+ onIdle: async () => {
131
+ log(`[ON_IDLE] Reply idle for session ${sessionId}, hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
132
+ // Send accumulated text with append=false and final=true
133
+ if (hasSentResponse && !finalSent) {
134
+ log(`[ON_IDLE] Sending accumulated text, length=${accumulatedText.length}`);
135
+ try {
136
+ // Send status update before final message
137
+ await (0, xy_formatter_js_1.sendStatusUpdate)({
138
+ config,
139
+ sessionId,
140
+ taskId,
141
+ messageId,
142
+ text: "任务处理已完成~",
143
+ state: "completed",
144
+ });
145
+ log(`[ON_IDLE] ✅ Sent completion status update`);
146
+ await (0, xy_formatter_js_1.sendA2AResponse)({
147
+ config,
148
+ sessionId,
149
+ taskId,
150
+ messageId,
151
+ text: accumulatedText,
152
+ append: false,
153
+ final: true,
154
+ });
155
+ finalSent = true;
156
+ log(`[ON_IDLE] Sent accumulated text`);
157
+ }
158
+ catch (err) {
159
+ error(`[ON_IDLE] Failed to send accumulated text:`, err);
160
+ }
161
+ }
162
+ else {
163
+ log(`[ON_IDLE] Skipping final message: hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
164
+ // Task was interrupted - send failure status and error response
165
+ try {
166
+ await (0, xy_formatter_js_1.sendStatusUpdate)({
167
+ config,
168
+ sessionId,
169
+ taskId,
170
+ messageId,
171
+ text: "任务处理中断了~",
172
+ state: "failed",
173
+ });
174
+ log(`[ON_IDLE] ✅ Sent failure status update`);
175
+ await (0, xy_formatter_js_1.sendA2AResponse)({
176
+ config,
177
+ sessionId,
178
+ taskId,
179
+ messageId,
180
+ text: "任务执行异常,请重试~",
181
+ append: false,
182
+ final: true,
183
+ });
184
+ finalSent = true;
185
+ log(`[ON_IDLE] ✅ Sent error response`);
186
+ }
187
+ catch (err) {
188
+ error(`[ON_IDLE] Failed to send failure status and error response:`, err);
189
+ }
190
+ }
191
+ // Stop status updates
192
+ stopStatusInterval();
193
+ },
194
+ onCleanup: () => {
195
+ log(`[ON_CLEANUP] Reply cleanup for session ${sessionId}, hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
196
+ },
197
+ });
198
+ return {
199
+ dispatcher,
200
+ replyOptions: {
201
+ ...replyOptions,
202
+ onModelSelected: prefixContext.onModelSelected,
203
+ // 🔧 Tool execution start callback
204
+ onToolStart: async ({ name, phase }) => {
205
+ log(`[TOOL START] 🔧 Tool execution started/updated: name=${name}, phase=${phase}, session=${sessionId}, taskId=${taskId}`);
206
+ if (phase === "start") {
207
+ const toolName = name || "unknown";
208
+ try {
209
+ await (0, xy_formatter_js_1.sendStatusUpdate)({
210
+ config,
211
+ sessionId,
212
+ taskId,
213
+ messageId,
214
+ text: `正在使用工具: ${toolName}...`,
215
+ state: "working",
216
+ });
217
+ log(`[TOOL START] ✅ Sent status update for tool start: ${toolName}`);
218
+ }
219
+ catch (err) {
220
+ error(`[TOOL START] ❌ Failed to send tool start status:`, err);
221
+ }
222
+ }
223
+ },
224
+ // 🔧 Tool execution result callback
225
+ onToolResult: async (payload) => {
226
+ const text = payload.text ?? "";
227
+ const hasMedia = Boolean(payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0);
228
+ log(`[TOOL RESULT] 🔧 Tool execution result received: session=${sessionId}, taskId=${taskId}`);
229
+ log(`[TOOL RESULT] - text.length=${text.length}`);
230
+ log(`[TOOL RESULT] - hasMedia=${hasMedia}`);
231
+ log(`[TOOL RESULT] - isError=${payload.isError}`);
232
+ if (text.length > 0) {
233
+ log(`[TOOL RESULT] - text preview: "${text.slice(0, 200)}"`);
234
+ }
235
+ try {
236
+ if (text.length > 0 || hasMedia) {
237
+ const resultText = text.length > 0 ? text : "工具执行完成";
238
+ await (0, xy_formatter_js_1.sendStatusUpdate)({
239
+ config,
240
+ sessionId,
241
+ taskId,
242
+ messageId,
243
+ text: resultText,
244
+ state: "working",
245
+ });
246
+ log(`[TOOL RESULT] ✅ Sent tool result as status update`);
247
+ }
248
+ }
249
+ catch (err) {
250
+ error(`[TOOL RESULT] ❌ Failed to send tool result status:`, err);
251
+ }
252
+ },
253
+ // 🧠 Reasoning/thinking process streaming callback
254
+ onReasoningStream: async (payload) => {
255
+ const text = payload.text ?? "";
256
+ log(`[REASONING STREAM] 🧠 Reasoning/thinking chunk received: session=${sessionId}, taskId=${taskId}`);
257
+ log(`[REASONING STREAM] - text.length=${text.length}`);
258
+ if (text.length > 0) {
259
+ log(`[REASONING STREAM] - text preview: "${text.slice(0, 200)}"`);
260
+ }
261
+ // try {
262
+ // if (text.length > 0) {
263
+ // await sendReasoningTextUpdate({
264
+ // config,
265
+ // sessionId,
266
+ // taskId,
267
+ // messageId,
268
+ // text,
269
+ // });
270
+ // log(`[REASONING STREAM] ✅ Sent reasoning chunk as reasoningText update`);
271
+ // }
272
+ // } catch (err) {
273
+ // error(`[REASONING STREAM] ❌ Failed to send reasoning chunk reasoningText:`, err);
274
+ // }
275
+ },
276
+ // 📝 Partial reply streaming callback (real-time preview)
277
+ onPartialReply: async (payload) => {
278
+ const text = payload.text ?? "";
279
+ const hasMedia = Boolean(payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0);
280
+ log(`[PARTIAL REPLY] 📝 Partial reply chunk received: session=${sessionId}, taskId=${taskId}`);
281
+ log(`[PARTIAL REPLY] - text.length=${text.length}`);
282
+ log(`[PARTIAL REPLY] - hasMedia=${hasMedia}`);
283
+ if (text.length > 0) {
284
+ log(`[PARTIAL REPLY] - text preview: "${text.slice(0, 200)}"`);
285
+ }
286
+ try {
287
+ if (text.length > 0) {
288
+ await (0, xy_formatter_js_1.sendReasoningTextUpdate)({
289
+ config,
290
+ sessionId,
291
+ taskId,
292
+ messageId,
293
+ text,
294
+ append: false,
295
+ });
296
+ log(`[PARTIAL REPLY] ✅ Sent partial reply as reasoningText update (append=false)`);
297
+ }
298
+ }
299
+ catch (err) {
300
+ error(`[PARTIAL REPLY] ❌ Failed to send partial reply reasoningText:`, err);
301
+ }
302
+ },
303
+ },
304
+ markDispatchIdle,
305
+ startStatusInterval, // Expose this to be called immediately
306
+ stopStatusInterval, // Expose this for manual control if needed
307
+ };
308
+ }
@@ -0,0 +1,29 @@
1
+ import type { XiaoYiChannelConfig } from "../types.js";
2
+ export interface SessionContext {
3
+ config: XiaoYiChannelConfig;
4
+ sessionId: string;
5
+ taskId: string;
6
+ messageId: string;
7
+ agentId: string;
8
+ }
9
+ /**
10
+ * Register a session context for tool access.
11
+ * Should be called when starting to process a message.
12
+ */
13
+ export declare function registerSession(sessionKey: string, context: SessionContext): void;
14
+ /**
15
+ * Unregister a session context.
16
+ * Should be called when message processing is complete.
17
+ */
18
+ export declare function unregisterSession(sessionKey: string): void;
19
+ /**
20
+ * Get session context by sessionKey.
21
+ * Returns null if session not found.
22
+ */
23
+ export declare function getSessionContext(sessionKey: string): SessionContext | null;
24
+ /**
25
+ * Get the most recent session context.
26
+ * This is a fallback for tools that don't have access to sessionKey.
27
+ * Returns null if no sessions are active.
28
+ */
29
+ export declare function getLatestSessionContext(): SessionContext | null;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSession = registerSession;
4
+ exports.unregisterSession = unregisterSession;
5
+ exports.getSessionContext = getSessionContext;
6
+ exports.getLatestSessionContext = getLatestSessionContext;
7
+ const logger_js_1 = require("../xy-utils/logger.js");
8
+ const config_manager_js_1 = require("../xy-utils/config-manager.js");
9
+ // Map of sessionKey -> SessionContext
10
+ const activeSessions = new Map();
11
+ /**
12
+ * Register a session context for tool access.
13
+ * Should be called when starting to process a message.
14
+ */
15
+ function registerSession(sessionKey, context) {
16
+ logger_js_1.logger.log(`[SESSION_MANAGER] 📝 Registering session: ${sessionKey}`);
17
+ logger_js_1.logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
18
+ logger_js_1.logger.log(`[SESSION_MANAGER] - taskId: ${context.taskId}`);
19
+ logger_js_1.logger.log(`[SESSION_MANAGER] - messageId: ${context.messageId}`);
20
+ logger_js_1.logger.log(`[SESSION_MANAGER] - agentId: ${context.agentId}`);
21
+ logger_js_1.logger.log(`[SESSION_MANAGER] - Active sessions before: ${activeSessions.size}`);
22
+ activeSessions.set(sessionKey, context);
23
+ logger_js_1.logger.log(`[SESSION_MANAGER] - Active sessions after: ${activeSessions.size}`);
24
+ logger_js_1.logger.log(`[SESSION_MANAGER] - All session keys: [${Array.from(activeSessions.keys()).join(", ")}]`);
25
+ }
26
+ /**
27
+ * Unregister a session context.
28
+ * Should be called when message processing is complete.
29
+ */
30
+ function unregisterSession(sessionKey) {
31
+ logger_js_1.logger.log(`[SESSION_MANAGER] 🗑️ Unregistering session: ${sessionKey}`);
32
+ logger_js_1.logger.log(`[SESSION_MANAGER] - Active sessions before: ${activeSessions.size}`);
33
+ logger_js_1.logger.log(`[SESSION_MANAGER] - Session existed: ${activeSessions.has(sessionKey)}`);
34
+ // Get session context before deleting to clear associated pushId
35
+ const context = activeSessions.get(sessionKey);
36
+ const existed = activeSessions.delete(sessionKey);
37
+ // Clear cached pushId for this session
38
+ if (context) {
39
+ config_manager_js_1.configManager.clearSession(context.sessionId);
40
+ }
41
+ logger_js_1.logger.log(`[SESSION_MANAGER] - Deleted: ${existed}`);
42
+ logger_js_1.logger.log(`[SESSION_MANAGER] - Active sessions after: ${activeSessions.size}`);
43
+ logger_js_1.logger.log(`[SESSION_MANAGER] - Remaining session keys: [${Array.from(activeSessions.keys()).join(", ")}]`);
44
+ }
45
+ /**
46
+ * Get session context by sessionKey.
47
+ * Returns null if session not found.
48
+ */
49
+ function getSessionContext(sessionKey) {
50
+ logger_js_1.logger.log(`[SESSION_MANAGER] 🔍 Getting session by key: ${sessionKey}`);
51
+ logger_js_1.logger.log(`[SESSION_MANAGER] - Active sessions: ${activeSessions.size}`);
52
+ const context = activeSessions.get(sessionKey) ?? null;
53
+ logger_js_1.logger.log(`[SESSION_MANAGER] - Found: ${context !== null}`);
54
+ if (context) {
55
+ logger_js_1.logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
56
+ }
57
+ return context;
58
+ }
59
+ /**
60
+ * Get the most recent session context.
61
+ * This is a fallback for tools that don't have access to sessionKey.
62
+ * Returns null if no sessions are active.
63
+ */
64
+ function getLatestSessionContext() {
65
+ logger_js_1.logger.log(`[SESSION_MANAGER] 🔍 Getting latest session context`);
66
+ logger_js_1.logger.log(`[SESSION_MANAGER] - Active sessions count: ${activeSessions.size}`);
67
+ logger_js_1.logger.log(`[SESSION_MANAGER] - Active session keys: [${Array.from(activeSessions.keys()).join(", ")}]`);
68
+ if (activeSessions.size === 0) {
69
+ logger_js_1.logger.error(`[SESSION_MANAGER] - ❌ No active sessions found!`);
70
+ return null;
71
+ }
72
+ // Return the last added session
73
+ const sessions = Array.from(activeSessions.values());
74
+ const latestSession = sessions[sessions.length - 1];
75
+ logger_js_1.logger.log(`[SESSION_MANAGER] - ✅ Found latest session:`);
76
+ logger_js_1.logger.log(`[SESSION_MANAGER] - sessionId: ${latestSession.sessionId}`);
77
+ logger_js_1.logger.log(`[SESSION_MANAGER] - taskId: ${latestSession.taskId}`);
78
+ logger_js_1.logger.log(`[SESSION_MANAGER] - messageId: ${latestSession.messageId}`);
79
+ return latestSession;
80
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Manages dynamic configuration updates that can change at runtime.
3
+ * Specifically handles pushId which can be updated per-session.
4
+ */
5
+ declare class ConfigManager {
6
+ private sessionPushIds;
7
+ private globalPushId;
8
+ /**
9
+ * Update push ID for a specific session.
10
+ */
11
+ updatePushId(sessionId: string, pushId: string): void;
12
+ /**
13
+ * Get push ID for a session (falls back to global if not found).
14
+ */
15
+ getPushId(sessionId?: string): string | null;
16
+ /**
17
+ * Clear push ID for a session.
18
+ */
19
+ clearSession(sessionId: string): void;
20
+ /**
21
+ * Clear all cached push IDs.
22
+ */
23
+ clear(): void;
24
+ }
25
+ export declare const configManager: ConfigManager;
26
+ export {};
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.configManager = void 0;
4
+ // Dynamic configuration manager for runtime updates
5
+ const logger_js_1 = require("./logger.js");
6
+ /**
7
+ * Manages dynamic configuration updates that can change at runtime.
8
+ * Specifically handles pushId which can be updated per-session.
9
+ */
10
+ class ConfigManager {
11
+ constructor() {
12
+ this.sessionPushIds = new Map();
13
+ this.globalPushId = null;
14
+ }
15
+ /**
16
+ * Update push ID for a specific session.
17
+ */
18
+ updatePushId(sessionId, pushId) {
19
+ if (!pushId) {
20
+ logger_js_1.logger.warn(`[ConfigManager] Attempted to set empty pushId for session ${sessionId}`);
21
+ return;
22
+ }
23
+ const previous = this.sessionPushIds.get(sessionId);
24
+ if (previous !== pushId) {
25
+ logger_js_1.logger.log(`[ConfigManager] ✨ Updated pushId for session ${sessionId}`);
26
+ logger_js_1.logger.log(`[ConfigManager] - Previous: ${previous ? previous.substring(0, 20) + '...' : 'none'}`);
27
+ logger_js_1.logger.log(`[ConfigManager] - New: ${pushId.substring(0, 20)}...`);
28
+ logger_js_1.logger.log(`[ConfigManager] - Full new pushId: ${pushId}`);
29
+ this.sessionPushIds.set(sessionId, pushId);
30
+ this.globalPushId = pushId; // Also update global for backward compatibility
31
+ }
32
+ }
33
+ /**
34
+ * Get push ID for a session (falls back to global if not found).
35
+ */
36
+ getPushId(sessionId) {
37
+ if (sessionId) {
38
+ const sessionPushId = this.sessionPushIds.get(sessionId);
39
+ if (sessionPushId) {
40
+ return sessionPushId;
41
+ }
42
+ }
43
+ return this.globalPushId;
44
+ }
45
+ /**
46
+ * Clear push ID for a session.
47
+ */
48
+ clearSession(sessionId) {
49
+ this.sessionPushIds.delete(sessionId);
50
+ logger_js_1.logger.debug(`[ConfigManager] Cleared pushId for session ${sessionId}`);
51
+ }
52
+ /**
53
+ * Clear all cached push IDs.
54
+ */
55
+ clear() {
56
+ this.sessionPushIds.clear();
57
+ this.globalPushId = null;
58
+ logger_js_1.logger.debug(`[ConfigManager] Cleared all cached pushIds`);
59
+ }
60
+ }
61
+ exports.configManager = new ConfigManager();
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Calculate SHA256 hash of a buffer.
3
+ */
4
+ export declare function calculateSHA256(buffer: Buffer): string;
5
+ /**
6
+ * Calculate SHA256 hash of a string.
7
+ */
8
+ export declare function calculateSHA256String(text: string): string;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.calculateSHA256 = calculateSHA256;
7
+ exports.calculateSHA256String = calculateSHA256String;
8
+ // Cryptographic utilities
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ /**
11
+ * Calculate SHA256 hash of a buffer.
12
+ */
13
+ function calculateSHA256(buffer) {
14
+ return crypto_1.default.createHash("sha256").update(buffer).digest("hex");
15
+ }
16
+ /**
17
+ * Calculate SHA256 hash of a string.
18
+ */
19
+ function calculateSHA256String(text) {
20
+ return crypto_1.default.createHash("sha256").update(text, "utf8").digest("hex");
21
+ }
@@ -0,0 +1,6 @@
1
+ export declare const logger: {
2
+ log(message: string, ...args: any[]): void;
3
+ warn(message: string, ...args: any[]): void;
4
+ error(message: string, ...args: any[]): void;
5
+ debug(message: string, ...args: any[]): void;
6
+ };
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logger = void 0;
4
+ // Logging utilities for XY channel
5
+ const runtime_js_1 = require("../runtime.js");
6
+ /**
7
+ * Log a message using the OpenClaw runtime logger.
8
+ */
9
+ function logMessage(level, message, ...args) {
10
+ try {
11
+ const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
12
+ const logFn = runtime[level];
13
+ if (logFn) {
14
+ const formattedMessage = `[XY] ${message}`;
15
+ logFn(formattedMessage, ...args);
16
+ }
17
+ }
18
+ catch (error) {
19
+ // Fallback to console if runtime not available
20
+ console[level](`[XY] ${message}`, ...args);
21
+ }
22
+ }
23
+ exports.logger = {
24
+ log(message, ...args) {
25
+ logMessage("log", message, ...args);
26
+ },
27
+ warn(message, ...args) {
28
+ logMessage("warn", message, ...args);
29
+ },
30
+ error(message, ...args) {
31
+ logMessage("error", message, ...args);
32
+ },
33
+ debug(message, ...args) {
34
+ // Debug messages go to log level
35
+ logMessage("log", `[DEBUG] ${message}`, ...args);
36
+ },
37
+ };
@@ -0,0 +1,34 @@
1
+ import type { SessionBinding, ServerIdentifier } from "../types.js";
2
+ /**
3
+ * Session-to-server binding cache.
4
+ * Tracks which WebSocket server each session is bound to.
5
+ */
6
+ declare class SessionManager {
7
+ private bindings;
8
+ /**
9
+ * Bind a session to a specific server.
10
+ */
11
+ bind(sessionId: string, server: ServerIdentifier): void;
12
+ /**
13
+ * Get the server binding for a session.
14
+ */
15
+ getBinding(sessionId: string): ServerIdentifier | null;
16
+ /**
17
+ * Check if a session is bound to a server.
18
+ */
19
+ isBound(sessionId: string): boolean;
20
+ /**
21
+ * Unbind a session.
22
+ */
23
+ unbind(sessionId: string): void;
24
+ /**
25
+ * Clear all bindings.
26
+ */
27
+ clear(): void;
28
+ /**
29
+ * Get all bindings.
30
+ */
31
+ getAll(): SessionBinding[];
32
+ }
33
+ export declare const sessionManager: SessionManager;
34
+ export {};
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sessionManager = void 0;
4
+ /**
5
+ * Session-to-server binding cache.
6
+ * Tracks which WebSocket server each session is bound to.
7
+ */
8
+ class SessionManager {
9
+ constructor() {
10
+ this.bindings = new Map();
11
+ }
12
+ /**
13
+ * Bind a session to a specific server.
14
+ */
15
+ bind(sessionId, server) {
16
+ this.bindings.set(sessionId, {
17
+ sessionId,
18
+ server,
19
+ boundAt: Date.now(),
20
+ });
21
+ }
22
+ /**
23
+ * Get the server binding for a session.
24
+ */
25
+ getBinding(sessionId) {
26
+ const binding = this.bindings.get(sessionId);
27
+ return binding ? binding.server : null;
28
+ }
29
+ /**
30
+ * Check if a session is bound to a server.
31
+ */
32
+ isBound(sessionId) {
33
+ return this.bindings.has(sessionId);
34
+ }
35
+ /**
36
+ * Unbind a session.
37
+ */
38
+ unbind(sessionId) {
39
+ this.bindings.delete(sessionId);
40
+ }
41
+ /**
42
+ * Clear all bindings.
43
+ */
44
+ clear() {
45
+ this.bindings.clear();
46
+ }
47
+ /**
48
+ * Get all bindings.
49
+ */
50
+ getAll() {
51
+ return Array.from(this.bindings.values());
52
+ }
53
+ }
54
+ // Singleton instance
55
+ exports.sessionManager = new SessionManager();