@ynhcj/xiaoyi-channel 1.0.5 โ 1.0.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.
- package/dist/src/client.d.ts +5 -0
- package/dist/src/client.js +52 -37
- package/dist/src/heartbeat.d.ts +2 -1
- package/dist/src/heartbeat.js +6 -1
- package/dist/src/monitor.d.ts +5 -0
- package/dist/src/monitor.js +29 -8
- package/dist/src/outbound.js +34 -1
- package/dist/src/websocket.d.ts +5 -0
- package/dist/src/websocket.js +26 -5
- package/package.json +1 -1
package/dist/src/client.d.ts
CHANGED
|
@@ -10,6 +10,11 @@ export declare function setClientRuntime(rt: RuntimeEnv | undefined): void;
|
|
|
10
10
|
* Reuses existing managers if config matches.
|
|
11
11
|
*/
|
|
12
12
|
export declare function getXYWebSocketManager(config: XYChannelConfig): XYWebSocketManager;
|
|
13
|
+
/**
|
|
14
|
+
* Remove a specific WebSocket manager from cache.
|
|
15
|
+
* Disconnects the manager and removes it from the cache.
|
|
16
|
+
*/
|
|
17
|
+
export declare function removeXYWebSocketManager(config: XYChannelConfig): void;
|
|
13
18
|
/**
|
|
14
19
|
* Clear all cached WebSocket managers.
|
|
15
20
|
*/
|
package/dist/src/client.js
CHANGED
|
@@ -34,6 +34,23 @@ export function getXYWebSocketManager(config) {
|
|
|
34
34
|
log(`[WS-MANAGER-CACHE] ๐ Total managers after creation: ${wsManagerCache.size}`);
|
|
35
35
|
return cached;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Remove a specific WebSocket manager from cache.
|
|
39
|
+
* Disconnects the manager and removes it from the cache.
|
|
40
|
+
*/
|
|
41
|
+
export function removeXYWebSocketManager(config) {
|
|
42
|
+
const cacheKey = `${config.apiKey}-${config.agentId}`;
|
|
43
|
+
const manager = wsManagerCache.get(cacheKey);
|
|
44
|
+
if (manager) {
|
|
45
|
+
console.log(`๐๏ธ [WS-MANAGER-CACHE] Removing manager from cache: ${cacheKey}`);
|
|
46
|
+
manager.disconnect();
|
|
47
|
+
wsManagerCache.delete(cacheKey);
|
|
48
|
+
console.log(`๐๏ธ [WS-MANAGER-CACHE] Manager removed, remaining managers: ${wsManagerCache.size}`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log(`โ ๏ธ [WS-MANAGER-CACHE] Manager not found in cache: ${cacheKey}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
37
54
|
/**
|
|
38
55
|
* Clear all cached WebSocket managers.
|
|
39
56
|
*/
|
|
@@ -56,77 +73,75 @@ export function getCachedManagerCount() {
|
|
|
56
73
|
* Helps identify connection issues and orphan connections.
|
|
57
74
|
*/
|
|
58
75
|
export function diagnoseAllManagers() {
|
|
59
|
-
|
|
60
|
-
log("
|
|
61
|
-
log("
|
|
62
|
-
log(
|
|
63
|
-
log(
|
|
64
|
-
log("");
|
|
76
|
+
console.log("========================================");
|
|
77
|
+
console.log("๐ WebSocket Manager Global Diagnostics");
|
|
78
|
+
console.log("========================================");
|
|
79
|
+
console.log(`Total cached managers: ${wsManagerCache.size}`);
|
|
80
|
+
console.log("");
|
|
65
81
|
if (wsManagerCache.size === 0) {
|
|
66
|
-
log("โน๏ธ No managers in cache");
|
|
67
|
-
log("========================================");
|
|
82
|
+
console.log("โน๏ธ No managers in cache");
|
|
83
|
+
console.log("========================================");
|
|
68
84
|
return;
|
|
69
85
|
}
|
|
70
86
|
let orphanCount = 0;
|
|
71
87
|
wsManagerCache.forEach((manager, key) => {
|
|
72
88
|
const diag = manager.getConnectionDiagnostics();
|
|
73
|
-
log(`๐ Manager: ${key}`);
|
|
74
|
-
log(` Shutting down: ${diag.isShuttingDown}`);
|
|
75
|
-
log(` Total event listeners on manager: ${diag.totalEventListeners}`);
|
|
89
|
+
console.log(`๐ Manager: ${key}`);
|
|
90
|
+
console.log(` Shutting down: ${diag.isShuttingDown}`);
|
|
91
|
+
console.log(` Total event listeners on manager: ${diag.totalEventListeners}`);
|
|
76
92
|
// Server 1
|
|
77
|
-
log(` ๐ Server1:`);
|
|
78
|
-
log(` - Exists: ${diag.server1.exists}`);
|
|
79
|
-
log(` - ReadyState: ${diag.server1.readyState}`);
|
|
80
|
-
log(` - State connected/ready: ${diag.server1.stateConnected}/${diag.server1.stateReady}`);
|
|
81
|
-
log(` - Reconnect attempts: ${diag.server1.reconnectAttempts}`);
|
|
82
|
-
log(` - Listeners on WebSocket: ${diag.server1.listenerCount}`);
|
|
83
|
-
log(` - Heartbeat active: ${diag.server1.heartbeatActive}`);
|
|
84
|
-
log(` - Has reconnect timer: ${diag.server1.hasReconnectTimer}`);
|
|
93
|
+
console.log(` ๐ Server1:`);
|
|
94
|
+
console.log(` - Exists: ${diag.server1.exists}`);
|
|
95
|
+
console.log(` - ReadyState: ${diag.server1.readyState}`);
|
|
96
|
+
console.log(` - State connected/ready: ${diag.server1.stateConnected}/${diag.server1.stateReady}`);
|
|
97
|
+
console.log(` - Reconnect attempts: ${diag.server1.reconnectAttempts}`);
|
|
98
|
+
console.log(` - Listeners on WebSocket: ${diag.server1.listenerCount}`);
|
|
99
|
+
console.log(` - Heartbeat active: ${diag.server1.heartbeatActive}`);
|
|
100
|
+
console.log(` - Has reconnect timer: ${diag.server1.hasReconnectTimer}`);
|
|
85
101
|
if (diag.server1.isOrphan) {
|
|
86
|
-
log(` โ ๏ธ ORPHAN CONNECTION DETECTED!`);
|
|
102
|
+
console.log(` โ ๏ธ ORPHAN CONNECTION DETECTED!`);
|
|
87
103
|
orphanCount++;
|
|
88
104
|
}
|
|
89
105
|
// Server 2
|
|
90
|
-
log(` ๐ Server2:`);
|
|
91
|
-
log(` - Exists: ${diag.server2.exists}`);
|
|
92
|
-
log(` - ReadyState: ${diag.server2.readyState}`);
|
|
93
|
-
log(` - State connected/ready: ${diag.server2.stateConnected}/${diag.server2.stateReady}`);
|
|
94
|
-
log(` - Reconnect attempts: ${diag.server2.reconnectAttempts}`);
|
|
95
|
-
log(` - Listeners on WebSocket: ${diag.server2.listenerCount}`);
|
|
96
|
-
log(` - Heartbeat active: ${diag.server2.heartbeatActive}`);
|
|
97
|
-
log(` - Has reconnect timer: ${diag.server2.hasReconnectTimer}`);
|
|
106
|
+
console.log(` ๐ Server2:`);
|
|
107
|
+
console.log(` - Exists: ${diag.server2.exists}`);
|
|
108
|
+
console.log(` - ReadyState: ${diag.server2.readyState}`);
|
|
109
|
+
console.log(` - State connected/ready: ${diag.server2.stateConnected}/${diag.server2.stateReady}`);
|
|
110
|
+
console.log(` - Reconnect attempts: ${diag.server2.reconnectAttempts}`);
|
|
111
|
+
console.log(` - Listeners on WebSocket: ${diag.server2.listenerCount}`);
|
|
112
|
+
console.log(` - Heartbeat active: ${diag.server2.heartbeatActive}`);
|
|
113
|
+
console.log(` - Has reconnect timer: ${diag.server2.hasReconnectTimer}`);
|
|
98
114
|
if (diag.server2.isOrphan) {
|
|
99
|
-
log(` โ ๏ธ ORPHAN CONNECTION DETECTED!`);
|
|
115
|
+
console.log(` โ ๏ธ ORPHAN CONNECTION DETECTED!`);
|
|
100
116
|
orphanCount++;
|
|
101
117
|
}
|
|
102
|
-
log("");
|
|
118
|
+
console.log("");
|
|
103
119
|
});
|
|
104
120
|
if (orphanCount > 0) {
|
|
105
|
-
log(`โ ๏ธ Total orphan connections found: ${orphanCount}`);
|
|
106
|
-
log(`๐ก Suggestion: These connections should be cleaned up`);
|
|
121
|
+
console.log(`โ ๏ธ Total orphan connections found: ${orphanCount}`);
|
|
122
|
+
console.log(`๐ก Suggestion: These connections should be cleaned up`);
|
|
107
123
|
}
|
|
108
124
|
else {
|
|
109
|
-
log(`โ
No orphan connections found`);
|
|
125
|
+
console.log(`โ
No orphan connections found`);
|
|
110
126
|
}
|
|
111
|
-
log("========================================");
|
|
127
|
+
console.log("========================================");
|
|
112
128
|
}
|
|
113
129
|
/**
|
|
114
130
|
* Clean up orphan connections across all managers.
|
|
115
131
|
* Returns the number of managers that had orphan connections.
|
|
116
132
|
*/
|
|
117
133
|
export function cleanupOrphanConnections() {
|
|
118
|
-
const log = runtime?.log ?? console.log;
|
|
119
134
|
let cleanedCount = 0;
|
|
120
135
|
wsManagerCache.forEach((manager, key) => {
|
|
121
136
|
const diag = manager.getConnectionDiagnostics();
|
|
122
137
|
if (diag.server1.isOrphan || diag.server2.isOrphan) {
|
|
123
|
-
log(`๐งน Cleaning up orphan connections in manager: ${key}`);
|
|
138
|
+
console.log(`๐งน Cleaning up orphan connections in manager: ${key}`);
|
|
124
139
|
manager.disconnect();
|
|
125
140
|
cleanedCount++;
|
|
126
141
|
}
|
|
127
142
|
});
|
|
128
143
|
if (cleanedCount > 0) {
|
|
129
|
-
log(`๐งน Cleaned up ${cleanedCount} manager(s) with orphan connections`);
|
|
144
|
+
console.log(`๐งน Cleaned up ${cleanedCount} manager(s) with orphan connections`);
|
|
130
145
|
}
|
|
131
146
|
return cleanedCount;
|
|
132
147
|
}
|
package/dist/src/heartbeat.d.ts
CHANGED
|
@@ -13,12 +13,13 @@ export declare class HeartbeatManager {
|
|
|
13
13
|
private config;
|
|
14
14
|
private onTimeout;
|
|
15
15
|
private serverName;
|
|
16
|
+
private onHeartbeatSuccess?;
|
|
16
17
|
private intervalTimer;
|
|
17
18
|
private timeoutTimer;
|
|
18
19
|
private lastPongTime;
|
|
19
20
|
private log;
|
|
20
21
|
private error;
|
|
21
|
-
constructor(ws: WebSocket, config: HeartbeatConfig, onTimeout: () => void, serverName?: string, logFn?: (msg: string, ...args: any[]) => void, errorFn?: (msg: string, ...args: any[]) => void);
|
|
22
|
+
constructor(ws: WebSocket, config: HeartbeatConfig, onTimeout: () => void, serverName?: string, logFn?: (msg: string, ...args: any[]) => void, errorFn?: (msg: string, ...args: any[]) => void, onHeartbeatSuccess?: () => void);
|
|
22
23
|
/**
|
|
23
24
|
* Start heartbeat monitoring.
|
|
24
25
|
*/
|
package/dist/src/heartbeat.js
CHANGED
|
@@ -9,17 +9,20 @@ export class HeartbeatManager {
|
|
|
9
9
|
config;
|
|
10
10
|
onTimeout;
|
|
11
11
|
serverName;
|
|
12
|
+
onHeartbeatSuccess;
|
|
12
13
|
intervalTimer = null;
|
|
13
14
|
timeoutTimer = null;
|
|
14
15
|
lastPongTime = 0;
|
|
15
16
|
// Logging functions following feishu pattern
|
|
16
17
|
log;
|
|
17
18
|
error;
|
|
18
|
-
constructor(ws, config, onTimeout, serverName = "unknown", logFn, errorFn
|
|
19
|
+
constructor(ws, config, onTimeout, serverName = "unknown", logFn, errorFn, onHeartbeatSuccess // โ
ๆฐๅข๏ผๅฟ่ทณๆๅๅ่ฐ
|
|
20
|
+
) {
|
|
19
21
|
this.ws = ws;
|
|
20
22
|
this.config = config;
|
|
21
23
|
this.onTimeout = onTimeout;
|
|
22
24
|
this.serverName = serverName;
|
|
25
|
+
this.onHeartbeatSuccess = onHeartbeatSuccess;
|
|
23
26
|
this.log = logFn ?? console.log;
|
|
24
27
|
this.error = errorFn ?? console.error;
|
|
25
28
|
}
|
|
@@ -36,6 +39,8 @@ export class HeartbeatManager {
|
|
|
36
39
|
clearTimeout(this.timeoutTimer);
|
|
37
40
|
this.timeoutTimer = null;
|
|
38
41
|
}
|
|
42
|
+
// โ
Report health: heartbeat successful
|
|
43
|
+
this.onHeartbeatSuccess?.();
|
|
39
44
|
});
|
|
40
45
|
// Start interval timer
|
|
41
46
|
this.intervalTimer = setInterval(() => {
|
package/dist/src/monitor.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ export type MonitorXYOpts = {
|
|
|
4
4
|
runtime?: RuntimeEnv;
|
|
5
5
|
abortSignal?: AbortSignal;
|
|
6
6
|
accountId?: string;
|
|
7
|
+
setStatus?: (status: {
|
|
8
|
+
lastEventAt?: number;
|
|
9
|
+
lastInboundAt?: number;
|
|
10
|
+
connected?: boolean;
|
|
11
|
+
}) => void;
|
|
7
12
|
};
|
|
8
13
|
/**
|
|
9
14
|
* Monitor XY channel WebSocket connections.
|
package/dist/src/monitor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveXYConfig } from "./config.js";
|
|
2
|
-
import { getXYWebSocketManager, diagnoseAllManagers, cleanupOrphanConnections } from "./client.js";
|
|
2
|
+
import { getXYWebSocketManager, diagnoseAllManagers, cleanupOrphanConnections, removeXYWebSocketManager } from "./client.js";
|
|
3
3
|
import { handleXYMessage } from "./bot.js";
|
|
4
4
|
/**
|
|
5
5
|
* Per-session serial queue that ensures messages from the same session are processed
|
|
@@ -37,11 +37,21 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
37
37
|
throw new Error(`XY account is disabled`);
|
|
38
38
|
}
|
|
39
39
|
const accountId = opts.accountId ?? "default";
|
|
40
|
+
// Create trackEvent function to report health to OpenClaw framework
|
|
41
|
+
const trackEvent = opts.setStatus
|
|
42
|
+
? () => {
|
|
43
|
+
opts.setStatus({ lastEventAt: Date.now(), lastInboundAt: Date.now() });
|
|
44
|
+
}
|
|
45
|
+
: undefined;
|
|
40
46
|
// ๐ Diagnose WebSocket managers before gateway start
|
|
41
|
-
log("๐ [DIAGNOSTICS] Checking WebSocket managers before gateway start...");
|
|
47
|
+
console.log("๐ [DIAGNOSTICS] Checking WebSocket managers before gateway start...");
|
|
42
48
|
diagnoseAllManagers();
|
|
43
49
|
// Get WebSocket manager (cached)
|
|
44
50
|
const wsManager = getXYWebSocketManager(account);
|
|
51
|
+
// โ
Set health event callback for heartbeat reporting
|
|
52
|
+
if (trackEvent) {
|
|
53
|
+
wsManager.setHealthEventCallback(trackEvent);
|
|
54
|
+
}
|
|
45
55
|
// Track logged servers to avoid duplicate logs
|
|
46
56
|
const loggedServers = new Set();
|
|
47
57
|
// Track active message processing to detect duplicates
|
|
@@ -55,6 +65,8 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
55
65
|
const messageHandler = (message, sessionId, serverId) => {
|
|
56
66
|
const messageKey = `${sessionId}::${message.id}`;
|
|
57
67
|
log(`[MONITOR-HANDLER] ####### messageHandler triggered: serverId=${serverId}, sessionId=${sessionId}, messageId=${message.id} #######`);
|
|
68
|
+
// โ
Report health: received a message
|
|
69
|
+
trackEvent?.();
|
|
58
70
|
// Check for duplicate message handling
|
|
59
71
|
if (activeMessages.has(messageKey)) {
|
|
60
72
|
error(`[MONITOR-HANDLER] โ ๏ธ WARNING: Duplicate message detected! messageKey=${messageKey}, this may cause duplicate dispatchers!`);
|
|
@@ -93,10 +105,17 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
93
105
|
log(`XY gateway: ${serverId} connected`);
|
|
94
106
|
loggedServers.add(serverId);
|
|
95
107
|
}
|
|
108
|
+
// โ
Report health: connection established
|
|
109
|
+
trackEvent?.();
|
|
110
|
+
opts.setStatus?.({ connected: true });
|
|
96
111
|
};
|
|
97
112
|
const disconnectedHandler = (serverId) => {
|
|
98
113
|
console.warn(`XY gateway: ${serverId} disconnected`);
|
|
99
114
|
loggedServers.delete(serverId);
|
|
115
|
+
// โ
Report disconnection status (only if all servers disconnected)
|
|
116
|
+
if (loggedServers.size === 0) {
|
|
117
|
+
opts.setStatus?.({ connected: false });
|
|
118
|
+
}
|
|
100
119
|
};
|
|
101
120
|
const errorHandler = (err, serverId) => {
|
|
102
121
|
error(`XY gateway: ${serverId} error: ${String(err)}`);
|
|
@@ -104,13 +123,13 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
104
123
|
const cleanup = () => {
|
|
105
124
|
log("XY gateway: cleaning up...");
|
|
106
125
|
// ๐ Diagnose before cleanup
|
|
107
|
-
log("๐ [DIAGNOSTICS] Checking WebSocket managers before cleanup...");
|
|
126
|
+
console.log("๐ [DIAGNOSTICS] Checking WebSocket managers before cleanup...");
|
|
108
127
|
diagnoseAllManagers();
|
|
109
128
|
// Stop health check interval
|
|
110
129
|
if (healthCheckInterval) {
|
|
111
130
|
clearInterval(healthCheckInterval);
|
|
112
131
|
healthCheckInterval = null;
|
|
113
|
-
log("โธ๏ธ Stopped periodic health check");
|
|
132
|
+
console.log("โธ๏ธ Stopped periodic health check");
|
|
114
133
|
}
|
|
115
134
|
// Remove event handlers to prevent duplicate calls on gateway restart
|
|
116
135
|
wsManager.off("message", messageHandler);
|
|
@@ -120,11 +139,13 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
120
139
|
// โ
Disconnect the wsManager to prevent connection leaks
|
|
121
140
|
// This is safe because each gateway lifecycle should have clean connections
|
|
122
141
|
wsManager.disconnect();
|
|
142
|
+
// โ
Remove manager from cache to prevent reusing dirty state
|
|
143
|
+
removeXYWebSocketManager(account);
|
|
123
144
|
loggedServers.clear();
|
|
124
145
|
activeMessages.clear();
|
|
125
146
|
log(`[MONITOR-HANDLER] ๐งน Cleanup complete, cleared active messages`);
|
|
126
147
|
// ๐ Diagnose after cleanup
|
|
127
|
-
log("๐ [DIAGNOSTICS] Checking WebSocket managers after cleanup...");
|
|
148
|
+
console.log("๐ [DIAGNOSTICS] Checking WebSocket managers after cleanup...");
|
|
128
149
|
diagnoseAllManagers();
|
|
129
150
|
};
|
|
130
151
|
const handleAbort = () => {
|
|
@@ -145,14 +166,14 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
145
166
|
wsManager.on("disconnected", disconnectedHandler);
|
|
146
167
|
wsManager.on("error", errorHandler);
|
|
147
168
|
// Start periodic health check (every 5 minutes)
|
|
148
|
-
log("๐ฅ Starting periodic health check (every 5 minutes)...");
|
|
169
|
+
console.log("๐ฅ Starting periodic health check (every 5 minutes)...");
|
|
149
170
|
healthCheckInterval = setInterval(() => {
|
|
150
|
-
log("๐ฅ [HEALTH CHECK] Periodic WebSocket diagnostics...");
|
|
171
|
+
console.log("๐ฅ [HEALTH CHECK] Periodic WebSocket diagnostics...");
|
|
151
172
|
diagnoseAllManagers();
|
|
152
173
|
// Auto-cleanup orphan connections
|
|
153
174
|
const cleaned = cleanupOrphanConnections();
|
|
154
175
|
if (cleaned > 0) {
|
|
155
|
-
log(`๐งน [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
|
|
176
|
+
console.log(`๐งน [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
|
|
156
177
|
}
|
|
157
178
|
}, 5 * 60 * 1000); // 5 minutes
|
|
158
179
|
// Connect to WebSocket servers
|
package/dist/src/outbound.js
CHANGED
|
@@ -4,6 +4,39 @@ import { XYPushService } from "./push.js";
|
|
|
4
4
|
import { getLatestSessionContext } from "./tools/session-manager.js";
|
|
5
5
|
// Special marker for default push delivery when no target is specified
|
|
6
6
|
const DEFAULT_PUSH_MARKER = "default";
|
|
7
|
+
// File extension to MIME type mapping
|
|
8
|
+
const FILE_TYPE_TO_MIME_TYPE = {
|
|
9
|
+
txt: "text/plain",
|
|
10
|
+
html: "text/html",
|
|
11
|
+
css: "text/css",
|
|
12
|
+
js: "application/javascript",
|
|
13
|
+
json: "application/json",
|
|
14
|
+
png: "image/png",
|
|
15
|
+
jpeg: "image/jpeg",
|
|
16
|
+
jpg: "image/jpeg",
|
|
17
|
+
gif: "image/gif",
|
|
18
|
+
svg: "image/svg+xml",
|
|
19
|
+
pdf: "application/pdf",
|
|
20
|
+
zip: "application/zip",
|
|
21
|
+
doc: "application/msword",
|
|
22
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
23
|
+
xls: "application/vnd.ms-excel",
|
|
24
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
25
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
26
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
27
|
+
mp3: "audio/mpeg",
|
|
28
|
+
mp4: "video/mp4",
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Get MIME type from file extension
|
|
32
|
+
*/
|
|
33
|
+
function getMimeTypeFromFilename(filename) {
|
|
34
|
+
const extension = filename.split(".").pop()?.toLowerCase();
|
|
35
|
+
if (extension && FILE_TYPE_TO_MIME_TYPE[extension]) {
|
|
36
|
+
return FILE_TYPE_TO_MIME_TYPE[extension];
|
|
37
|
+
}
|
|
38
|
+
return "text/plain"; // Default fallback
|
|
39
|
+
}
|
|
7
40
|
/**
|
|
8
41
|
* Outbound adapter for sending messages from OpenClaw to XY.
|
|
9
42
|
* Uses Push service for direct message delivery.
|
|
@@ -128,7 +161,7 @@ export const xyOutbound = {
|
|
|
128
161
|
// Get filename and mime type from mediaUrl
|
|
129
162
|
// mediaUrl may be a local file path or URL
|
|
130
163
|
const fileName = mediaUrl.split("/").pop() || "unknown";
|
|
131
|
-
const mimeType =
|
|
164
|
+
const mimeType = getMimeTypeFromFilename(fileName);
|
|
132
165
|
// Build agent_response message
|
|
133
166
|
const agentResponse = {
|
|
134
167
|
msgType: "agent_response",
|
package/dist/src/websocket.d.ts
CHANGED
|
@@ -52,7 +52,12 @@ export declare class XYWebSocketManager extends EventEmitter {
|
|
|
52
52
|
private isShuttingDown;
|
|
53
53
|
private log;
|
|
54
54
|
private error;
|
|
55
|
+
private onHealthEvent?;
|
|
55
56
|
constructor(config: XYChannelConfig, runtime?: RuntimeEnv);
|
|
57
|
+
/**
|
|
58
|
+
* Set health event callback to report activity to OpenClaw framework.
|
|
59
|
+
*/
|
|
60
|
+
setHealthEventCallback(callback: () => void): void;
|
|
56
61
|
/**
|
|
57
62
|
* Check if config matches the current instance.
|
|
58
63
|
*/
|
package/dist/src/websocket.js
CHANGED
|
@@ -41,6 +41,8 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
41
41
|
// Logging functions following feishu pattern
|
|
42
42
|
log;
|
|
43
43
|
error;
|
|
44
|
+
// Health event callback
|
|
45
|
+
onHealthEvent;
|
|
44
46
|
constructor(config, runtime) {
|
|
45
47
|
super();
|
|
46
48
|
this.config = config;
|
|
@@ -48,6 +50,12 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
48
50
|
this.log = runtime?.log ?? console.log;
|
|
49
51
|
this.error = runtime?.error ?? console.error;
|
|
50
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Set health event callback to report activity to OpenClaw framework.
|
|
55
|
+
*/
|
|
56
|
+
setHealthEventCallback(callback) {
|
|
57
|
+
this.onHealthEvent = callback;
|
|
58
|
+
}
|
|
51
59
|
/**
|
|
52
60
|
* Check if config matches the current instance.
|
|
53
61
|
*/
|
|
@@ -218,14 +226,23 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
218
226
|
*/
|
|
219
227
|
async connectServer(serverId, url) {
|
|
220
228
|
return new Promise((resolve, reject) => {
|
|
221
|
-
|
|
229
|
+
// Check if URL is wss with IP address to bypass certificate validation
|
|
230
|
+
const urlObj = new URL(url);
|
|
231
|
+
const isWssWithIP = urlObj.protocol === 'wss:' && /^(\d{1,3}\.){3}\d{1,3}$/.test(urlObj.hostname);
|
|
232
|
+
const wsOptions = {
|
|
222
233
|
headers: {
|
|
223
234
|
"x-uid": this.config.uid,
|
|
224
235
|
"x-api-key": this.config.apiKey,
|
|
225
236
|
"x-agent-id": this.config.agentId,
|
|
226
237
|
"x-request-from": "openclaw",
|
|
227
238
|
},
|
|
228
|
-
}
|
|
239
|
+
};
|
|
240
|
+
// Bypass certificate validation for wss with IP address
|
|
241
|
+
if (isWssWithIP) {
|
|
242
|
+
this.log(`${serverId}: Bypassing certificate validation for IP address: ${urlObj.hostname}`);
|
|
243
|
+
wsOptions.rejectUnauthorized = false;
|
|
244
|
+
}
|
|
245
|
+
const ws = new WebSocket(url, wsOptions);
|
|
229
246
|
const state = serverId === "server1" ? this.state1 : this.state2;
|
|
230
247
|
// Set the WebSocket instance
|
|
231
248
|
if (serverId === "server1") {
|
|
@@ -340,7 +357,8 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
340
357
|
}, () => {
|
|
341
358
|
this.error(`Heartbeat timeout for ${serverId}, reconnecting...`);
|
|
342
359
|
this.reconnectServer(serverId);
|
|
343
|
-
}, serverId, this.log, this.error
|
|
360
|
+
}, serverId, this.log, this.error, this.onHealthEvent // โ
Pass health event callback
|
|
361
|
+
);
|
|
344
362
|
heartbeat.start();
|
|
345
363
|
if (serverId === "server1") {
|
|
346
364
|
this.heartbeat1 = heartbeat;
|
|
@@ -416,9 +434,12 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
416
434
|
// Wrapped format (InboundWebSocketMessage)
|
|
417
435
|
const inboundMsg = parsed;
|
|
418
436
|
console.log(`[XY-${serverId}] Message type: Wrapped, msgType: ${inboundMsg.msgType}`);
|
|
419
|
-
//
|
|
437
|
+
// Handle heartbeat responses
|
|
420
438
|
if (inboundMsg.msgType === "heartbeat") {
|
|
421
|
-
console.log(`[XY-${serverId}]
|
|
439
|
+
console.log(`[XY-${serverId}] Received heartbeat response`);
|
|
440
|
+
// โ
Report health: application-level heartbeat received
|
|
441
|
+
// This prevents openclaw health-monitor from marking connection as stale
|
|
442
|
+
this.onHealthEvent?.();
|
|
422
443
|
return;
|
|
423
444
|
}
|
|
424
445
|
// Handle data messages (e.g., intent execution results)
|