@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.
- package/dist/auth.d.ts +1 -1
- package/dist/channel.d.ts +116 -14
- package/dist/channel.js +199 -665
- package/dist/config-schema.d.ts +8 -8
- package/dist/config-schema.js +5 -5
- package/dist/file-download.d.ts +17 -0
- package/dist/file-download.js +69 -0
- package/dist/heartbeat.d.ts +39 -0
- package/dist/heartbeat.js +102 -0
- package/dist/index.d.ts +1 -4
- package/dist/index.js +7 -11
- package/dist/push.d.ts +28 -0
- package/dist/push.js +135 -0
- package/dist/runtime.d.ts +48 -2
- package/dist/runtime.js +117 -3
- package/dist/types.d.ts +95 -1
- package/dist/websocket.d.ts +49 -1
- package/dist/websocket.js +279 -20
- 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 +187 -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/package.json +32 -16
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendA2AResponse = sendA2AResponse;
|
|
4
|
+
exports.sendReasoningTextUpdate = sendReasoningTextUpdate;
|
|
5
|
+
exports.sendStatusUpdate = sendStatusUpdate;
|
|
6
|
+
exports.sendCommand = sendCommand;
|
|
7
|
+
exports.sendClearContextResponse = sendClearContextResponse;
|
|
8
|
+
exports.sendTasksCancelResponse = sendTasksCancelResponse;
|
|
9
|
+
// OpenClaw β A2A format conversion
|
|
10
|
+
const uuid_1 = require("uuid");
|
|
11
|
+
const xy_client_js_1 = require("./xy-client.js");
|
|
12
|
+
const runtime_js_1 = require("./runtime.js");
|
|
13
|
+
/**
|
|
14
|
+
* Send an A2A artifact update response.
|
|
15
|
+
*/
|
|
16
|
+
async function sendA2AResponse(params) {
|
|
17
|
+
const { config, sessionId, taskId, messageId, text, append, final, files } = params;
|
|
18
|
+
const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
|
|
19
|
+
const log = runtime?.log ?? console.log;
|
|
20
|
+
const error = runtime?.error ?? console.error;
|
|
21
|
+
// Build artifact update event
|
|
22
|
+
const artifact = {
|
|
23
|
+
taskId,
|
|
24
|
+
kind: "artifact-update",
|
|
25
|
+
append,
|
|
26
|
+
lastChunk: true,
|
|
27
|
+
final,
|
|
28
|
+
artifact: {
|
|
29
|
+
artifactId: (0, uuid_1.v4)(),
|
|
30
|
+
parts: [],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
// Add text part (even if empty string, to maintain parts structure)
|
|
34
|
+
if (text !== undefined) {
|
|
35
|
+
artifact.artifact.parts.push({
|
|
36
|
+
kind: "text",
|
|
37
|
+
text,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// Add file parts if provided
|
|
41
|
+
if (files && files.length > 0) {
|
|
42
|
+
artifact.artifact.parts.push({
|
|
43
|
+
kind: "data",
|
|
44
|
+
data: { fileInfo: files },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// Build JSON-RPC response
|
|
48
|
+
const jsonRpcResponse = {
|
|
49
|
+
jsonrpc: "2.0",
|
|
50
|
+
id: messageId,
|
|
51
|
+
result: artifact,
|
|
52
|
+
};
|
|
53
|
+
// Send via WebSocket
|
|
54
|
+
const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
|
|
55
|
+
const outboundMessage = {
|
|
56
|
+
msgType: "agent_response",
|
|
57
|
+
agentId: config.agentId,
|
|
58
|
+
sessionId,
|
|
59
|
+
taskId,
|
|
60
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
61
|
+
};
|
|
62
|
+
// π Log complete response body
|
|
63
|
+
log(`[A2A_RESPONSE] π€ Sending A2A artifact-update response:`);
|
|
64
|
+
log(`[A2A_RESPONSE] - sessionId: ${sessionId}`);
|
|
65
|
+
log(`[A2A_RESPONSE] - taskId: ${taskId}`);
|
|
66
|
+
log(`[A2A_RESPONSE] - messageId: ${messageId}`);
|
|
67
|
+
log(`[A2A_RESPONSE] - append: ${append}`);
|
|
68
|
+
log(`[A2A_RESPONSE] - final: ${final}`);
|
|
69
|
+
log(`[A2A_RESPONSE] - text length: ${text?.length ?? 0}`);
|
|
70
|
+
log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
|
|
71
|
+
log(`[A2A_RESPONSE] π¦ Complete outbound message:`);
|
|
72
|
+
log(JSON.stringify(outboundMessage, null, 2));
|
|
73
|
+
log(`[A2A_RESPONSE] π¦ JSON-RPC response body:`);
|
|
74
|
+
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
75
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
76
|
+
log(`[A2A_RESPONSE] β
Message sent successfully`);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Send an A2A artifact-update with reasoningText part.
|
|
80
|
+
* Used for onToolStart, onToolResult, onReasoningStream, onReasoningEnd, onPartialReply.
|
|
81
|
+
* append=true, final=false, lastChunk=true, text is suffixed with newline for markdown rendering.
|
|
82
|
+
*/
|
|
83
|
+
async function sendReasoningTextUpdate(params) {
|
|
84
|
+
const { config, sessionId, taskId, messageId, text, append = true } = params;
|
|
85
|
+
const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
|
|
86
|
+
const log = runtime?.log ?? console.log;
|
|
87
|
+
const error = runtime?.error ?? console.error;
|
|
88
|
+
const artifact = {
|
|
89
|
+
taskId,
|
|
90
|
+
kind: "artifact-update",
|
|
91
|
+
append,
|
|
92
|
+
lastChunk: true,
|
|
93
|
+
final: false,
|
|
94
|
+
artifact: {
|
|
95
|
+
artifactId: (0, uuid_1.v4)(),
|
|
96
|
+
parts: [
|
|
97
|
+
{
|
|
98
|
+
kind: "reasoningText",
|
|
99
|
+
reasoningText: text,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
const jsonRpcResponse = {
|
|
105
|
+
jsonrpc: "2.0",
|
|
106
|
+
id: messageId,
|
|
107
|
+
result: artifact,
|
|
108
|
+
};
|
|
109
|
+
const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
|
|
110
|
+
const outboundMessage = {
|
|
111
|
+
msgType: "agent_response",
|
|
112
|
+
agentId: config.agentId,
|
|
113
|
+
sessionId,
|
|
114
|
+
taskId,
|
|
115
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
116
|
+
};
|
|
117
|
+
log(`[REASONING_TEXT] π€ Sending reasoningText update: sessionId=${sessionId}, taskId=${taskId}, text.length=${text.length}`);
|
|
118
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
119
|
+
log(`[REASONING_TEXT] β
Sent successfully`);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Send an A2A task status update.
|
|
123
|
+
* Follows A2A protocol standard format with nested status object.
|
|
124
|
+
*/
|
|
125
|
+
async function sendStatusUpdate(params) {
|
|
126
|
+
const { config, sessionId, taskId, messageId, text, state } = params;
|
|
127
|
+
const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
|
|
128
|
+
const log = runtime?.log ?? console.log;
|
|
129
|
+
const error = runtime?.error ?? console.error;
|
|
130
|
+
// Build status update event following A2A protocol standard
|
|
131
|
+
const statusUpdate = {
|
|
132
|
+
taskId,
|
|
133
|
+
kind: "status-update",
|
|
134
|
+
final: false, // Status updates should not end the stream
|
|
135
|
+
status: {
|
|
136
|
+
message: {
|
|
137
|
+
role: "agent",
|
|
138
|
+
parts: [
|
|
139
|
+
{
|
|
140
|
+
kind: "text",
|
|
141
|
+
text,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
state,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
// Build JSON-RPC response
|
|
149
|
+
const jsonRpcResponse = {
|
|
150
|
+
jsonrpc: "2.0",
|
|
151
|
+
id: messageId,
|
|
152
|
+
result: statusUpdate,
|
|
153
|
+
};
|
|
154
|
+
// Send via WebSocket
|
|
155
|
+
const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
|
|
156
|
+
const outboundMessage = {
|
|
157
|
+
msgType: "agent_response",
|
|
158
|
+
agentId: config.agentId,
|
|
159
|
+
sessionId,
|
|
160
|
+
taskId,
|
|
161
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
162
|
+
};
|
|
163
|
+
// π Log complete response body
|
|
164
|
+
log(`[A2A_STATUS] π€ Sending A2A status-update:`);
|
|
165
|
+
log(`[A2A_STATUS] - sessionId: ${sessionId}`);
|
|
166
|
+
log(`[A2A_STATUS] - taskId: ${taskId}`);
|
|
167
|
+
log(`[A2A_STATUS] - messageId: ${messageId}`);
|
|
168
|
+
log(`[A2A_STATUS] - state: ${state}`);
|
|
169
|
+
log(`[A2A_STATUS] - text: "${text}"`);
|
|
170
|
+
log(`[A2A_STATUS] π¦ Complete outbound message:`);
|
|
171
|
+
log(JSON.stringify(outboundMessage, null, 2));
|
|
172
|
+
log(`[A2A_STATUS] π¦ JSON-RPC response body:`);
|
|
173
|
+
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
174
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
175
|
+
log(`[A2A_STATUS] β
Status update sent successfully`);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Send a command as an artifact update (final=false).
|
|
179
|
+
*/
|
|
180
|
+
async function sendCommand(params) {
|
|
181
|
+
const { config, sessionId, taskId, messageId, command } = params;
|
|
182
|
+
const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
|
|
183
|
+
const log = runtime?.log ?? console.log;
|
|
184
|
+
const error = runtime?.error ?? console.error;
|
|
185
|
+
// Build artifact update with command as data
|
|
186
|
+
// Wrap command in commands array as per protocol requirement
|
|
187
|
+
const artifact = {
|
|
188
|
+
taskId,
|
|
189
|
+
kind: "artifact-update",
|
|
190
|
+
append: false,
|
|
191
|
+
lastChunk: true,
|
|
192
|
+
final: false, // Commands are not final
|
|
193
|
+
artifact: {
|
|
194
|
+
artifactId: (0, uuid_1.v4)(),
|
|
195
|
+
parts: [
|
|
196
|
+
{
|
|
197
|
+
kind: "data",
|
|
198
|
+
data: {
|
|
199
|
+
commands: [command],
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
// Build JSON-RPC response
|
|
206
|
+
const jsonRpcResponse = {
|
|
207
|
+
jsonrpc: "2.0",
|
|
208
|
+
id: messageId,
|
|
209
|
+
result: artifact,
|
|
210
|
+
};
|
|
211
|
+
// Send via WebSocket
|
|
212
|
+
const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
|
|
213
|
+
const outboundMessage = {
|
|
214
|
+
msgType: "agent_response",
|
|
215
|
+
agentId: config.agentId,
|
|
216
|
+
sessionId,
|
|
217
|
+
taskId,
|
|
218
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
219
|
+
};
|
|
220
|
+
// π Log complete response body
|
|
221
|
+
log(`[A2A_COMMAND] π€ Sending A2A command:`);
|
|
222
|
+
log(`[A2A_COMMAND] - sessionId: ${sessionId}`);
|
|
223
|
+
log(`[A2A_COMMAND] - taskId: ${taskId}`);
|
|
224
|
+
log(`[A2A_COMMAND] - messageId: ${messageId}`);
|
|
225
|
+
log(`[A2A_COMMAND] - command: ${command.header.namespace}::${command.header.name}`);
|
|
226
|
+
log(`[A2A_COMMAND] π¦ Complete outbound message:`);
|
|
227
|
+
log(JSON.stringify(outboundMessage, null, 2));
|
|
228
|
+
log(`[A2A_COMMAND] π¦ JSON-RPC response body:`);
|
|
229
|
+
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
230
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
231
|
+
log(`[A2A_COMMAND] β
Command sent successfully`);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Send a clearContext response.
|
|
235
|
+
*/
|
|
236
|
+
async function sendClearContextResponse(params) {
|
|
237
|
+
const { config, sessionId, messageId } = params;
|
|
238
|
+
const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
|
|
239
|
+
const log = runtime?.log ?? console.log;
|
|
240
|
+
const error = runtime?.error ?? console.error;
|
|
241
|
+
// Build JSON-RPC response for clearContext
|
|
242
|
+
const jsonRpcResponse = {
|
|
243
|
+
jsonrpc: "2.0",
|
|
244
|
+
id: messageId,
|
|
245
|
+
result: {
|
|
246
|
+
status: {
|
|
247
|
+
state: "cleared",
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
error: {
|
|
251
|
+
code: 0,
|
|
252
|
+
// Note: Using any to bypass type check as the response format differs from standard A2A types
|
|
253
|
+
message: "",
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
// Send via WebSocket
|
|
257
|
+
const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
|
|
258
|
+
const outboundMessage = {
|
|
259
|
+
msgType: "agent_response",
|
|
260
|
+
agentId: config.agentId,
|
|
261
|
+
sessionId,
|
|
262
|
+
taskId: sessionId, // Use sessionId as taskId for clearContext
|
|
263
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
264
|
+
};
|
|
265
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
266
|
+
log(`Sent clearContext response: sessionId=${sessionId}`);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Send a tasks/cancel response.
|
|
270
|
+
*/
|
|
271
|
+
async function sendTasksCancelResponse(params) {
|
|
272
|
+
const { config, sessionId, taskId, messageId } = params;
|
|
273
|
+
const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
|
|
274
|
+
const log = runtime?.log ?? console.log;
|
|
275
|
+
const error = runtime?.error ?? console.error;
|
|
276
|
+
// Build JSON-RPC response for tasks/cancel
|
|
277
|
+
// Note: Using any to bypass type check as the response format differs from standard A2A types
|
|
278
|
+
const jsonRpcResponse = {
|
|
279
|
+
jsonrpc: "2.0",
|
|
280
|
+
id: messageId,
|
|
281
|
+
result: {
|
|
282
|
+
id: taskId,
|
|
283
|
+
status: {
|
|
284
|
+
state: "canceled",
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
error: {
|
|
288
|
+
code: 0,
|
|
289
|
+
message: "",
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
// Send via WebSocket
|
|
293
|
+
const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
|
|
294
|
+
const outboundMessage = {
|
|
295
|
+
msgType: "agent_response",
|
|
296
|
+
agentId: config.agentId,
|
|
297
|
+
sessionId,
|
|
298
|
+
taskId,
|
|
299
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
300
|
+
};
|
|
301
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
302
|
+
log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
|
|
303
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RuntimeEnv } from "openclaw/dist/plugin-sdk/index.js";
|
|
2
|
+
export type MonitorXYOpts = {
|
|
3
|
+
config?: any;
|
|
4
|
+
runtime?: RuntimeEnv;
|
|
5
|
+
abortSignal?: AbortSignal;
|
|
6
|
+
accountId?: string;
|
|
7
|
+
setStatus?: (status: {
|
|
8
|
+
lastEventAt?: number;
|
|
9
|
+
lastInboundAt?: number;
|
|
10
|
+
connected?: boolean;
|
|
11
|
+
}) => void;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Monitor XY channel WebSocket connections.
|
|
15
|
+
* Keeps the connection alive until abortSignal is triggered.
|
|
16
|
+
*/
|
|
17
|
+
export declare function monitorXYProvider(opts?: MonitorXYOpts): Promise<void>;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.monitorXYProvider = monitorXYProvider;
|
|
4
|
+
const xy_config_js_1 = require("./xy-config.js");
|
|
5
|
+
const xy_client_js_1 = require("./xy-client.js");
|
|
6
|
+
const xy_bot_js_1 = require("./xy-bot.js");
|
|
7
|
+
/**
|
|
8
|
+
* Per-session serial queue that ensures messages from the same session are processed
|
|
9
|
+
* in arrival order while allowing different sessions to run concurrently.
|
|
10
|
+
* Following feishu/monitor.account.ts pattern.
|
|
11
|
+
*/
|
|
12
|
+
function createSessionQueue() {
|
|
13
|
+
const queues = new Map();
|
|
14
|
+
return (sessionId, task) => {
|
|
15
|
+
const prev = queues.get(sessionId) ?? Promise.resolve();
|
|
16
|
+
const next = prev.then(task, task);
|
|
17
|
+
queues.set(sessionId, next);
|
|
18
|
+
void next.finally(() => {
|
|
19
|
+
if (queues.get(sessionId) === next) {
|
|
20
|
+
queues.delete(sessionId);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return next;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Monitor XY channel WebSocket connections.
|
|
28
|
+
* Keeps the connection alive until abortSignal is triggered.
|
|
29
|
+
*/
|
|
30
|
+
async function monitorXYProvider(opts = {}) {
|
|
31
|
+
const cfg = opts.config;
|
|
32
|
+
if (!cfg) {
|
|
33
|
+
throw new Error("Config is required for XY monitor");
|
|
34
|
+
}
|
|
35
|
+
const runtime = opts.runtime;
|
|
36
|
+
const log = runtime?.log ?? console.log;
|
|
37
|
+
const error = runtime?.error ?? console.error;
|
|
38
|
+
const account = (0, xy_config_js_1.resolveXYConfig)(cfg);
|
|
39
|
+
if (!account.enabled) {
|
|
40
|
+
throw new Error(`XY account is disabled`);
|
|
41
|
+
}
|
|
42
|
+
const accountId = opts.accountId ?? "default";
|
|
43
|
+
// Create trackEvent function to report health to OpenClaw framework
|
|
44
|
+
const trackEvent = opts.setStatus
|
|
45
|
+
? () => {
|
|
46
|
+
opts.setStatus({ lastEventAt: Date.now(), lastInboundAt: Date.now() });
|
|
47
|
+
}
|
|
48
|
+
: undefined;
|
|
49
|
+
// π Diagnose WebSocket managers before gateway start
|
|
50
|
+
// console.log("π [DIAGNOSTICS] Checking WebSocket managers before gateway start...");
|
|
51
|
+
// diagnoseAllManagers();
|
|
52
|
+
// Get WebSocket manager (cached)
|
|
53
|
+
const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(account);
|
|
54
|
+
// β
Set health event callback for heartbeat reporting
|
|
55
|
+
// This ensures OpenClaw's health-monitor sees activity and doesn't trigger stale-socket restarts
|
|
56
|
+
if (trackEvent) {
|
|
57
|
+
wsManager.setHealthEventCallback(trackEvent);
|
|
58
|
+
}
|
|
59
|
+
// Track logged servers to avoid duplicate logs
|
|
60
|
+
const loggedServers = new Set();
|
|
61
|
+
// Track active message processing to detect duplicates
|
|
62
|
+
const activeMessages = new Set();
|
|
63
|
+
// Create session queue for ordered message processing
|
|
64
|
+
const enqueue = createSessionQueue();
|
|
65
|
+
// Health check interval
|
|
66
|
+
let healthCheckInterval = null;
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
// Event handlers (defined early so they can be referenced in cleanup)
|
|
69
|
+
const messageHandler = (message, sessionId, serverId) => {
|
|
70
|
+
const messageKey = `${sessionId}::${message.id}`;
|
|
71
|
+
log(`[MONITOR-HANDLER] ####### messageHandler triggered: serverId=${serverId}, sessionId=${sessionId}, messageId=${message.id} #######`);
|
|
72
|
+
// β
Report health: received a message
|
|
73
|
+
trackEvent?.();
|
|
74
|
+
// Check for duplicate message handling
|
|
75
|
+
if (activeMessages.has(messageKey)) {
|
|
76
|
+
error(`[MONITOR-HANDLER] β οΈ WARNING: Duplicate message detected! messageKey=${messageKey}, this may cause duplicate dispatchers!`);
|
|
77
|
+
}
|
|
78
|
+
activeMessages.add(messageKey);
|
|
79
|
+
log(`[MONITOR-HANDLER] π Active messages count: ${activeMessages.size}, messageKey: ${messageKey}`);
|
|
80
|
+
const task = async () => {
|
|
81
|
+
try {
|
|
82
|
+
log(`[MONITOR-HANDLER] π Starting handleXYMessage for messageKey=${messageKey}`);
|
|
83
|
+
await (0, xy_bot_js_1.handleXYMessage)({
|
|
84
|
+
cfg,
|
|
85
|
+
runtime,
|
|
86
|
+
message,
|
|
87
|
+
accountId, // β
Pass accountId ("default")
|
|
88
|
+
});
|
|
89
|
+
log(`[MONITOR-HANDLER] β
Completed handleXYMessage for messageKey=${messageKey}`);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
// β
Only log error, don't re-throw to prevent gateway restart
|
|
93
|
+
error(`XY gateway: error handling message from ${serverId}: ${String(err)}`);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
// Remove from active messages when done
|
|
97
|
+
activeMessages.delete(messageKey);
|
|
98
|
+
log(`[MONITOR-HANDLER] π§Ή Cleaned up messageKey=${messageKey}, remaining active: ${activeMessages.size}`);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
void enqueue(sessionId, task).catch((err) => {
|
|
102
|
+
// Error already logged in task, this is for queue failures
|
|
103
|
+
error(`XY gateway: queue processing failed for session ${sessionId}: ${String(err)}`);
|
|
104
|
+
activeMessages.delete(messageKey);
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
const connectedHandler = (serverId) => {
|
|
108
|
+
if (!loggedServers.has(serverId)) {
|
|
109
|
+
log(`XY gateway: ${serverId} connected`);
|
|
110
|
+
loggedServers.add(serverId);
|
|
111
|
+
}
|
|
112
|
+
// β
Report health: connection established
|
|
113
|
+
trackEvent?.();
|
|
114
|
+
opts.setStatus?.({ connected: true });
|
|
115
|
+
};
|
|
116
|
+
const disconnectedHandler = (serverId) => {
|
|
117
|
+
console.warn(`XY gateway: ${serverId} disconnected`);
|
|
118
|
+
loggedServers.delete(serverId);
|
|
119
|
+
// β
Report disconnection status (only if all servers disconnected)
|
|
120
|
+
if (loggedServers.size === 0) {
|
|
121
|
+
opts.setStatus?.({ connected: false });
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const errorHandler = (err, serverId) => {
|
|
125
|
+
error(`XY gateway: ${serverId} error: ${String(err)}`);
|
|
126
|
+
};
|
|
127
|
+
const cleanup = () => {
|
|
128
|
+
log("XY gateway: cleaning up...");
|
|
129
|
+
// Stop health check interval
|
|
130
|
+
if (healthCheckInterval) {
|
|
131
|
+
clearInterval(healthCheckInterval);
|
|
132
|
+
healthCheckInterval = null;
|
|
133
|
+
console.log("βΈοΈ Stopped periodic health check");
|
|
134
|
+
}
|
|
135
|
+
// Remove event handlers to prevent duplicate calls on gateway restart
|
|
136
|
+
wsManager.off("message", messageHandler);
|
|
137
|
+
wsManager.off("connected", connectedHandler);
|
|
138
|
+
wsManager.off("disconnected", disconnectedHandler);
|
|
139
|
+
wsManager.off("error", errorHandler);
|
|
140
|
+
// β
Remove manager from cache - this will also disconnect
|
|
141
|
+
// removeXYWebSocketManager internally calls manager.disconnect()
|
|
142
|
+
(0, xy_client_js_1.removeXYWebSocketManager)(account);
|
|
143
|
+
loggedServers.clear();
|
|
144
|
+
activeMessages.clear();
|
|
145
|
+
log(`[MONITOR-HANDLER] π§Ή Cleanup complete, cleared active messages`);
|
|
146
|
+
};
|
|
147
|
+
const handleAbort = () => {
|
|
148
|
+
log("XY gateway: abort signal received, stopping");
|
|
149
|
+
cleanup();
|
|
150
|
+
log("XY gateway stopped");
|
|
151
|
+
resolve();
|
|
152
|
+
};
|
|
153
|
+
if (opts.abortSignal?.aborted) {
|
|
154
|
+
cleanup();
|
|
155
|
+
resolve();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
opts.abortSignal?.addEventListener("abort", handleAbort, { once: true });
|
|
159
|
+
// Register event handlers (handlers are defined above in cleanup scope)
|
|
160
|
+
wsManager.on("message", messageHandler);
|
|
161
|
+
wsManager.on("connected", connectedHandler);
|
|
162
|
+
wsManager.on("disconnected", disconnectedHandler);
|
|
163
|
+
wsManager.on("error", errorHandler);
|
|
164
|
+
// Start periodic health check (every 5 minutes)
|
|
165
|
+
console.log("π₯ Starting periodic health check (every 5 minutes)...");
|
|
166
|
+
healthCheckInterval = setInterval(() => {
|
|
167
|
+
console.log("π₯ [HEALTH CHECK] Periodic WebSocket diagnostics...");
|
|
168
|
+
// diagnoseAllManagers();
|
|
169
|
+
// // Auto-cleanup orphan connections
|
|
170
|
+
// const cleaned = cleanupOrphanConnections();
|
|
171
|
+
// if (cleaned > 0) {
|
|
172
|
+
// console.log(`π§Ή [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
|
|
173
|
+
// }
|
|
174
|
+
}, 5 * 60 * 1000); // 5 minutes
|
|
175
|
+
// Connect to WebSocket servers
|
|
176
|
+
wsManager.connect()
|
|
177
|
+
.then(() => {
|
|
178
|
+
log("XY gateway: started successfully");
|
|
179
|
+
})
|
|
180
|
+
.catch((err) => {
|
|
181
|
+
// Connection failed but don't reject - continue monitoring for reconnection
|
|
182
|
+
error(`XY gateway: initial connection failed: ${String(err)}`);
|
|
183
|
+
// Still resolve successfully so plugin starts
|
|
184
|
+
resolve();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { A2AJsonRpcRequest, A2AMessagePart, A2ADataEvent } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parsed message information extracted from A2A request.
|
|
4
|
+
* Note: agentId is not extracted from message - it should come from config.
|
|
5
|
+
*/
|
|
6
|
+
export interface ParsedA2AMessage {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
taskId: string;
|
|
9
|
+
messageId: string;
|
|
10
|
+
parts: A2AMessagePart[];
|
|
11
|
+
method: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse an A2A JSON-RPC request into structured message data.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseA2AMessage(request: A2AJsonRpcRequest): ParsedA2AMessage;
|
|
17
|
+
/**
|
|
18
|
+
* Extract text content from message parts.
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractTextFromParts(parts: A2AMessagePart[]): string;
|
|
21
|
+
/**
|
|
22
|
+
* Extract file parts from message parts.
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractFileParts(parts: A2AMessagePart[]): Array<{
|
|
25
|
+
name: string;
|
|
26
|
+
mimeType: string;
|
|
27
|
+
uri: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Extract data events from message parts (for tool responses).
|
|
31
|
+
*/
|
|
32
|
+
export declare function extractDataEvents(parts: A2AMessagePart[]): A2ADataEvent[];
|
|
33
|
+
/**
|
|
34
|
+
* Check if message is a clearContext request.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isClearContextMessage(method: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Check if message is a tasks/cancel request.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isTasksCancelMessage(method: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Extract push_id from message parts.
|
|
43
|
+
* Looks for push_id in data parts under variables.systemVariables.push_id
|
|
44
|
+
*/
|
|
45
|
+
export declare function extractPushId(parts: A2AMessagePart[]): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Validate A2A request structure.
|
|
48
|
+
*/
|
|
49
|
+
export declare function validateA2ARequest(request: any): request is A2AJsonRpcRequest;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseA2AMessage = parseA2AMessage;
|
|
4
|
+
exports.extractTextFromParts = extractTextFromParts;
|
|
5
|
+
exports.extractFileParts = extractFileParts;
|
|
6
|
+
exports.extractDataEvents = extractDataEvents;
|
|
7
|
+
exports.isClearContextMessage = isClearContextMessage;
|
|
8
|
+
exports.isTasksCancelMessage = isTasksCancelMessage;
|
|
9
|
+
exports.extractPushId = extractPushId;
|
|
10
|
+
exports.validateA2ARequest = validateA2ARequest;
|
|
11
|
+
const logger_js_1 = require("./xy-utils/logger.js");
|
|
12
|
+
/**
|
|
13
|
+
* Parse an A2A JSON-RPC request into structured message data.
|
|
14
|
+
*/
|
|
15
|
+
function parseA2AMessage(request) {
|
|
16
|
+
const { method, params, id } = request;
|
|
17
|
+
if (!params) {
|
|
18
|
+
throw new Error("A2A request missing params");
|
|
19
|
+
}
|
|
20
|
+
const { sessionId, message, id: paramsId } = params;
|
|
21
|
+
if (!sessionId || !message) {
|
|
22
|
+
throw new Error("A2A request params missing required fields");
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
sessionId,
|
|
26
|
+
taskId: paramsId, // Task ID from params (ε―Ήθ―ε―δΈζ θ―)
|
|
27
|
+
messageId: id, // Global unique message sequence ID from top-level request
|
|
28
|
+
parts: message.parts || [],
|
|
29
|
+
method,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extract text content from message parts.
|
|
34
|
+
*/
|
|
35
|
+
function extractTextFromParts(parts) {
|
|
36
|
+
const textParts = parts
|
|
37
|
+
.filter((part) => part.kind === "text")
|
|
38
|
+
.map((part) => part.text);
|
|
39
|
+
return textParts.join("\n").trim();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extract file parts from message parts.
|
|
43
|
+
*/
|
|
44
|
+
function extractFileParts(parts) {
|
|
45
|
+
return parts
|
|
46
|
+
.filter((part) => part.kind === "file")
|
|
47
|
+
.map((part) => part.file);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Extract data events from message parts (for tool responses).
|
|
51
|
+
*/
|
|
52
|
+
function extractDataEvents(parts) {
|
|
53
|
+
return parts
|
|
54
|
+
.filter((part) => part.kind === "data")
|
|
55
|
+
.map((part) => part.data.event)
|
|
56
|
+
.filter((event) => event !== undefined);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check if message is a clearContext request.
|
|
60
|
+
*/
|
|
61
|
+
function isClearContextMessage(method) {
|
|
62
|
+
return method === "clearContext" || method === "clear_context";
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if message is a tasks/cancel request.
|
|
66
|
+
*/
|
|
67
|
+
function isTasksCancelMessage(method) {
|
|
68
|
+
return method === "tasks/cancel" || method === "tasks_cancel";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Extract push_id from message parts.
|
|
72
|
+
* Looks for push_id in data parts under variables.systemVariables.push_id
|
|
73
|
+
*/
|
|
74
|
+
function extractPushId(parts) {
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
if (part.kind === "data" && part.data) {
|
|
77
|
+
const pushId = part.data.variables?.systemVariables?.push_id;
|
|
78
|
+
if (pushId && typeof pushId === "string") {
|
|
79
|
+
return pushId;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Validate A2A request structure.
|
|
87
|
+
*/
|
|
88
|
+
function validateA2ARequest(request) {
|
|
89
|
+
if (!request || typeof request !== "object") {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (request.jsonrpc !== "2.0") {
|
|
93
|
+
logger_js_1.logger.warn("Invalid JSON-RPC version:", request.jsonrpc);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (!request.method || typeof request.method !== "string") {
|
|
97
|
+
logger_js_1.logger.warn("Missing or invalid method");
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (!request.id) {
|
|
101
|
+
logger_js_1.logger.warn("Missing request id");
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
if (!request.params || typeof request.params !== "object") {
|
|
105
|
+
logger_js_1.logger.warn("Missing or invalid params");
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { OpenClawConfig, RuntimeEnv } from "openclaw/dist/plugin-sdk/index.js";
|
|
2
|
+
type ClawdbotConfig = OpenClawConfig;
|
|
3
|
+
export interface CreateXYReplyDispatcherParams {
|
|
4
|
+
cfg: ClawdbotConfig;
|
|
5
|
+
runtime: RuntimeEnv;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
taskId: string;
|
|
8
|
+
messageId: string;
|
|
9
|
+
accountId: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create a reply dispatcher for XY channel messages.
|
|
13
|
+
* Follows feishu pattern with status updates and streaming support.
|
|
14
|
+
* Runtime is expected to be validated before calling this function.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createXYReplyDispatcher(params: CreateXYReplyDispatcherParams): any;
|
|
17
|
+
export {};
|