@ynhcj/xiaoyi-channel 0.0.6 β 0.0.7-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/dist/src/bot.js +38 -9
- package/dist/src/channel.js +4 -1
- package/dist/src/client.d.ts +15 -0
- package/dist/src/client.js +97 -2
- package/dist/src/file-download.js +10 -1
- package/dist/src/file-upload.js +1 -1
- package/dist/src/formatter.d.ts +17 -0
- package/dist/src/formatter.js +48 -1
- package/dist/src/heartbeat.d.ts +2 -1
- package/dist/src/heartbeat.js +9 -1
- package/dist/src/monitor.d.ts +5 -0
- package/dist/src/monitor.js +97 -21
- package/dist/src/onboarding.js +7 -7
- package/dist/src/outbound.js +73 -8
- package/dist/src/parser.d.ts +5 -0
- package/dist/src/parser.js +15 -0
- package/dist/src/push.d.ts +7 -1
- package/dist/src/push.js +110 -19
- package/dist/src/reply-dispatcher.js +123 -16
- package/dist/src/tools/calendar-tool.d.ts +6 -0
- package/dist/src/tools/calendar-tool.js +167 -0
- package/dist/src/tools/location-tool.js +16 -6
- package/dist/src/tools/note-tool.js +4 -4
- package/dist/src/tools/search-note-tool.js +4 -4
- package/dist/src/tools/session-manager.js +7 -0
- package/dist/src/types.d.ts +5 -9
- package/dist/src/utils/config-manager.d.ts +26 -0
- package/dist/src/utils/config-manager.js +56 -0
- package/dist/src/websocket.d.ts +39 -0
- package/dist/src/websocket.js +145 -33
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { getXYRuntime } from "./runtime.js";
|
|
2
2
|
import { createXYReplyDispatcher } from "./reply-dispatcher.js";
|
|
3
|
-
import { parseA2AMessage, extractTextFromParts, extractFileParts } from "./parser.js";
|
|
3
|
+
import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId } from "./parser.js";
|
|
4
4
|
import { downloadFilesFromParts } from "./file-download.js";
|
|
5
5
|
import { resolveXYConfig } from "./config.js";
|
|
6
6
|
import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse } from "./formatter.js";
|
|
7
7
|
import { registerSession, unregisterSession } from "./tools/session-manager.js";
|
|
8
|
+
import { configManager } from "./utils/config-manager.js";
|
|
8
9
|
/**
|
|
9
10
|
* Handle an incoming A2A message.
|
|
10
11
|
* This is the main entry point for message processing.
|
|
@@ -19,7 +20,8 @@ export async function handleXYMessage(params) {
|
|
|
19
20
|
try {
|
|
20
21
|
// Check for special messages BEFORE parsing (these have different param structures)
|
|
21
22
|
const messageMethod = message.method;
|
|
22
|
-
log(`[
|
|
23
|
+
log(`[BOT-ENTRY] <<<<<<< Received message with method: ${messageMethod}, id: ${message.id} >>>>>>>`);
|
|
24
|
+
log(`[BOT-ENTRY] Stack trace for debugging:`, new Error().stack?.split('\n').slice(1, 4).join('\n'));
|
|
23
25
|
// Handle clearContext messages (params only has sessionId)
|
|
24
26
|
if (messageMethod === "clearContext" || messageMethod === "clear_context") {
|
|
25
27
|
const sessionId = message.params?.sessionId;
|
|
@@ -54,6 +56,18 @@ export async function handleXYMessage(params) {
|
|
|
54
56
|
}
|
|
55
57
|
// Parse the A2A message (for regular messages)
|
|
56
58
|
const parsed = parseA2AMessage(message);
|
|
59
|
+
// Extract and update push_id if present
|
|
60
|
+
const pushId = extractPushId(parsed.parts);
|
|
61
|
+
if (pushId) {
|
|
62
|
+
log(`[BOT] π Extracted push_id from user message`);
|
|
63
|
+
log(`[BOT] - Session ID: ${parsed.sessionId}`);
|
|
64
|
+
log(`[BOT] - Push ID preview: ${pushId.substring(0, 20)}...`);
|
|
65
|
+
log(`[BOT] - Full push_id: ${pushId}`);
|
|
66
|
+
configManager.updatePushId(parsed.sessionId, pushId);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
log(`[BOT] βΉοΈ No push_id found in message, will use config default`);
|
|
70
|
+
}
|
|
57
71
|
// Resolve configuration (needed for status updates)
|
|
58
72
|
const config = resolveXYConfig(cfg);
|
|
59
73
|
// β
Resolve agent route (following feishu pattern)
|
|
@@ -61,7 +75,7 @@ export async function handleXYMessage(params) {
|
|
|
61
75
|
// Use sessionId as peer.id to ensure all messages in the same session share context
|
|
62
76
|
let route = core.channel.routing.resolveAgentRoute({
|
|
63
77
|
cfg,
|
|
64
|
-
channel: "
|
|
78
|
+
channel: "xiaoyi-channel",
|
|
65
79
|
accountId, // "default"
|
|
66
80
|
peer: {
|
|
67
81
|
kind: "direct",
|
|
@@ -82,6 +96,18 @@ export async function handleXYMessage(params) {
|
|
|
82
96
|
agentId: route.accountId,
|
|
83
97
|
});
|
|
84
98
|
log(`[BOT] β
Session registered for tools`);
|
|
99
|
+
// Send initial status update immediately after parsing message
|
|
100
|
+
log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
|
|
101
|
+
void sendStatusUpdate({
|
|
102
|
+
config,
|
|
103
|
+
sessionId: parsed.sessionId,
|
|
104
|
+
taskId: parsed.taskId,
|
|
105
|
+
messageId: parsed.messageId,
|
|
106
|
+
text: "δ»»ε‘ζ£ε¨ε€ηδΈοΌθ―·η¨ε~",
|
|
107
|
+
state: "working",
|
|
108
|
+
}).catch((err) => {
|
|
109
|
+
error(`Failed to send initial status update:`, err);
|
|
110
|
+
});
|
|
85
111
|
// Extract text and files from parts
|
|
86
112
|
const text = extractTextFromParts(parsed.parts);
|
|
87
113
|
const fileParts = extractFileParts(parsed.parts);
|
|
@@ -98,7 +124,7 @@ export async function handleXYMessage(params) {
|
|
|
98
124
|
messageBody = `${speaker}: ${messageBody}`;
|
|
99
125
|
// Format agent envelope (following feishu pattern)
|
|
100
126
|
const body = core.channel.reply.formatAgentEnvelope({
|
|
101
|
-
channel: "
|
|
127
|
+
channel: "xiaoyi-channel",
|
|
102
128
|
from: speaker,
|
|
103
129
|
timestamp: new Date(),
|
|
104
130
|
envelope: envelopeOptions,
|
|
@@ -118,13 +144,13 @@ export async function handleXYMessage(params) {
|
|
|
118
144
|
GroupSubject: undefined,
|
|
119
145
|
SenderName: parsed.sessionId,
|
|
120
146
|
SenderId: parsed.sessionId,
|
|
121
|
-
Provider: "
|
|
122
|
-
Surface: "
|
|
147
|
+
Provider: "xiaoyi-channel",
|
|
148
|
+
Surface: "xiaoyi-channel",
|
|
123
149
|
MessageSid: parsed.messageId,
|
|
124
150
|
Timestamp: Date.now(),
|
|
125
151
|
WasMentioned: false,
|
|
126
152
|
CommandAuthorized: true,
|
|
127
|
-
OriginatingChannel: "
|
|
153
|
+
OriginatingChannel: "xiaoyi-channel",
|
|
128
154
|
OriginatingTo: parsed.sessionId, // Original message target
|
|
129
155
|
ReplyToBody: undefined, // A2A protocol doesn't support reply/quote
|
|
130
156
|
...mediaPayload,
|
|
@@ -142,6 +168,7 @@ export async function handleXYMessage(params) {
|
|
|
142
168
|
error(`Failed to send initial status update:`, err);
|
|
143
169
|
});
|
|
144
170
|
// Create reply dispatcher (following feishu pattern)
|
|
171
|
+
log(`[BOT-DISPATCHER] π― Creating reply dispatcher for session=${parsed.sessionId}, taskId=${parsed.taskId}, messageId=${parsed.messageId}`);
|
|
145
172
|
const { dispatcher, replyOptions, markDispatchIdle, startStatusInterval } = createXYReplyDispatcher({
|
|
146
173
|
cfg,
|
|
147
174
|
runtime,
|
|
@@ -150,6 +177,7 @@ export async function handleXYMessage(params) {
|
|
|
150
177
|
messageId: parsed.messageId,
|
|
151
178
|
accountId: route.accountId, // β
Use route.accountId
|
|
152
179
|
});
|
|
180
|
+
log(`[BOT-DISPATCHER] β
Reply dispatcher created successfully`);
|
|
153
181
|
// Start status update interval (will send updates every 60 seconds)
|
|
154
182
|
// Interval will be automatically stopped when onIdle/onCleanup is triggered
|
|
155
183
|
startStatusInterval();
|
|
@@ -177,6 +205,7 @@ export async function handleXYMessage(params) {
|
|
|
177
205
|
log(`xy: dispatch complete (session=${parsed.sessionId})`);
|
|
178
206
|
}
|
|
179
207
|
catch (err) {
|
|
208
|
+
// β
Only log error, don't re-throw to prevent gateway restart
|
|
180
209
|
error("Failed to handle XY message:", err);
|
|
181
210
|
runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
|
|
182
211
|
log(`[BOT] β Error occurred, attempting cleanup...`);
|
|
@@ -189,7 +218,7 @@ export async function handleXYMessage(params) {
|
|
|
189
218
|
log(`[BOT] π§Ή Cleaning up session after error: ${sessionId}`);
|
|
190
219
|
const route = core.channel.routing.resolveAgentRoute({
|
|
191
220
|
cfg,
|
|
192
|
-
channel: "
|
|
221
|
+
channel: "xiaoyi-channel",
|
|
193
222
|
accountId,
|
|
194
223
|
peer: {
|
|
195
224
|
kind: "direct",
|
|
@@ -205,7 +234,7 @@ export async function handleXYMessage(params) {
|
|
|
205
234
|
log(`[BOT] β οΈ Cleanup failed:`, cleanupErr);
|
|
206
235
|
// Ignore cleanup errors
|
|
207
236
|
}
|
|
208
|
-
throw
|
|
237
|
+
// β Don't re-throw: message processing error should not affect gateway stability
|
|
209
238
|
}
|
|
210
239
|
}
|
|
211
240
|
/**
|
package/dist/src/channel.js
CHANGED
|
@@ -5,6 +5,7 @@ import { xyOnboardingAdapter } from "./onboarding.js";
|
|
|
5
5
|
import { locationTool } from "./tools/location-tool.js";
|
|
6
6
|
import { noteTool } from "./tools/note-tool.js";
|
|
7
7
|
import { searchNoteTool } from "./tools/search-note-tool.js";
|
|
8
|
+
import { calendarTool } from "./tools/calendar-tool.js";
|
|
8
9
|
/**
|
|
9
10
|
* Xiaoyi Channel Plugin for OpenClaw.
|
|
10
11
|
* Implements Xiaoyi A2A protocol with dual WebSocket connections.
|
|
@@ -22,6 +23,7 @@ export const xyPlugin = {
|
|
|
22
23
|
agentPrompt: {
|
|
23
24
|
messageToolHints: () => [
|
|
24
25
|
"- xiaoyi targeting: omit `target` to reply to the current conversation (auto-inferred). Explicit targets: `default`",
|
|
26
|
+
"- If the user requests a file, you can call the message tool with the xiaoyi-channel channel to return it. Note: sendMedia requires a text reply."
|
|
25
27
|
],
|
|
26
28
|
},
|
|
27
29
|
capabilities: {
|
|
@@ -43,7 +45,7 @@ export const xyPlugin = {
|
|
|
43
45
|
},
|
|
44
46
|
outbound: xyOutbound,
|
|
45
47
|
onboarding: xyOnboardingAdapter,
|
|
46
|
-
agentTools: [locationTool, noteTool, searchNoteTool],
|
|
48
|
+
agentTools: [locationTool, noteTool, searchNoteTool, calendarTool],
|
|
47
49
|
messaging: {
|
|
48
50
|
normalizeTarget: (raw) => {
|
|
49
51
|
const trimmed = raw.trim();
|
|
@@ -79,6 +81,7 @@ export const xyPlugin = {
|
|
|
79
81
|
runtime: context.runtime,
|
|
80
82
|
abortSignal: context.abortSignal,
|
|
81
83
|
accountId: context.accountId,
|
|
84
|
+
setStatus: context.setStatus,
|
|
82
85
|
});
|
|
83
86
|
},
|
|
84
87
|
},
|
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
|
*/
|
|
@@ -18,3 +23,13 @@ export declare function clearXYWebSocketManagers(): void;
|
|
|
18
23
|
* Get the number of cached managers.
|
|
19
24
|
*/
|
|
20
25
|
export declare function getCachedManagerCount(): number;
|
|
26
|
+
/**
|
|
27
|
+
* Diagnose all cached WebSocket managers.
|
|
28
|
+
* Helps identify connection issues and orphan connections.
|
|
29
|
+
*/
|
|
30
|
+
export declare function diagnoseAllManagers(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Clean up orphan connections across all managers.
|
|
33
|
+
* Returns the number of managers that had orphan connections.
|
|
34
|
+
*/
|
|
35
|
+
export declare function cleanupOrphanConnections(): number;
|
package/dist/src/client.js
CHANGED
|
@@ -23,16 +23,34 @@ export function getXYWebSocketManager(config) {
|
|
|
23
23
|
let cached = wsManagerCache.get(cacheKey);
|
|
24
24
|
if (cached && cached.isConfigMatch(config)) {
|
|
25
25
|
const log = runtime?.log ?? console.log;
|
|
26
|
-
log(`[
|
|
26
|
+
log(`[WS-MANAGER-CACHE] β
Reusing cached WebSocket manager: ${cacheKey}, total managers: ${wsManagerCache.size}`);
|
|
27
27
|
return cached;
|
|
28
28
|
}
|
|
29
29
|
// Create new manager
|
|
30
30
|
const log = runtime?.log ?? console.log;
|
|
31
|
-
log(`Creating new WebSocket manager: ${cacheKey}`);
|
|
31
|
+
log(`[WS-MANAGER-CACHE] π Creating new WebSocket manager: ${cacheKey}, total managers before: ${wsManagerCache.size}`);
|
|
32
32
|
cached = new XYWebSocketManager(config, runtime);
|
|
33
33
|
wsManagerCache.set(cacheKey, cached);
|
|
34
|
+
log(`[WS-MANAGER-CACHE] π Total managers after creation: ${wsManagerCache.size}`);
|
|
34
35
|
return cached;
|
|
35
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
|
+
}
|
|
36
54
|
/**
|
|
37
55
|
* Clear all cached WebSocket managers.
|
|
38
56
|
*/
|
|
@@ -50,3 +68,80 @@ export function clearXYWebSocketManagers() {
|
|
|
50
68
|
export function getCachedManagerCount() {
|
|
51
69
|
return wsManagerCache.size;
|
|
52
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Diagnose all cached WebSocket managers.
|
|
73
|
+
* Helps identify connection issues and orphan connections.
|
|
74
|
+
*/
|
|
75
|
+
export function diagnoseAllManagers() {
|
|
76
|
+
console.log("========================================");
|
|
77
|
+
console.log("π WebSocket Manager Global Diagnostics");
|
|
78
|
+
console.log("========================================");
|
|
79
|
+
console.log(`Total cached managers: ${wsManagerCache.size}`);
|
|
80
|
+
console.log("");
|
|
81
|
+
if (wsManagerCache.size === 0) {
|
|
82
|
+
console.log("βΉοΈ No managers in cache");
|
|
83
|
+
console.log("========================================");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let orphanCount = 0;
|
|
87
|
+
wsManagerCache.forEach((manager, key) => {
|
|
88
|
+
const diag = manager.getConnectionDiagnostics();
|
|
89
|
+
console.log(`π Manager: ${key}`);
|
|
90
|
+
console.log(` Shutting down: ${diag.isShuttingDown}`);
|
|
91
|
+
console.log(` Total event listeners on manager: ${diag.totalEventListeners}`);
|
|
92
|
+
// Server 1
|
|
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}`);
|
|
101
|
+
if (diag.server1.isOrphan) {
|
|
102
|
+
console.log(` β οΈ ORPHAN CONNECTION DETECTED!`);
|
|
103
|
+
orphanCount++;
|
|
104
|
+
}
|
|
105
|
+
// Server 2
|
|
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}`);
|
|
114
|
+
if (diag.server2.isOrphan) {
|
|
115
|
+
console.log(` β οΈ ORPHAN CONNECTION DETECTED!`);
|
|
116
|
+
orphanCount++;
|
|
117
|
+
}
|
|
118
|
+
console.log("");
|
|
119
|
+
});
|
|
120
|
+
if (orphanCount > 0) {
|
|
121
|
+
console.log(`β οΈ Total orphan connections found: ${orphanCount}`);
|
|
122
|
+
console.log(`π‘ Suggestion: These connections should be cleaned up`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log(`β
No orphan connections found`);
|
|
126
|
+
}
|
|
127
|
+
console.log("========================================");
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Clean up orphan connections across all managers.
|
|
131
|
+
* Returns the number of managers that had orphan connections.
|
|
132
|
+
*/
|
|
133
|
+
export function cleanupOrphanConnections() {
|
|
134
|
+
let cleanedCount = 0;
|
|
135
|
+
wsManagerCache.forEach((manager, key) => {
|
|
136
|
+
const diag = manager.getConnectionDiagnostics();
|
|
137
|
+
if (diag.server1.isOrphan || diag.server2.isOrphan) {
|
|
138
|
+
console.log(`π§Ή Cleaning up orphan connections in manager: ${key}`);
|
|
139
|
+
manager.disconnect();
|
|
140
|
+
cleanedCount++;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
if (cleanedCount > 0) {
|
|
144
|
+
console.log(`π§Ή Cleaned up ${cleanedCount} manager(s) with orphan connections`);
|
|
145
|
+
}
|
|
146
|
+
return cleanedCount;
|
|
147
|
+
}
|
|
@@ -8,8 +8,10 @@ import { logger } from "./utils/logger.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export async function downloadFile(url, destPath) {
|
|
10
10
|
logger.debug(`Downloading file from ${url} to ${destPath}`);
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
const timeout = setTimeout(() => controller.abort(), 30000); // 30 seconds timeout
|
|
11
13
|
try {
|
|
12
|
-
const response = await fetch(url);
|
|
14
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
13
15
|
if (!response.ok) {
|
|
14
16
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
15
17
|
}
|
|
@@ -19,9 +21,16 @@ export async function downloadFile(url, destPath) {
|
|
|
19
21
|
logger.debug(`File downloaded successfully: ${destPath}`);
|
|
20
22
|
}
|
|
21
23
|
catch (error) {
|
|
24
|
+
if (error.name === 'AbortError') {
|
|
25
|
+
logger.error(`Download timeout (30s) for ${url}`);
|
|
26
|
+
throw new Error(`Download timeout after 30 seconds`);
|
|
27
|
+
}
|
|
22
28
|
logger.error(`Failed to download file from ${url}:`, error);
|
|
23
29
|
throw error;
|
|
24
30
|
}
|
|
31
|
+
finally {
|
|
32
|
+
clearTimeout(timeout);
|
|
33
|
+
}
|
|
25
34
|
}
|
|
26
35
|
/**
|
|
27
36
|
* Download files from A2A file parts.
|
package/dist/src/file-upload.js
CHANGED
package/dist/src/formatter.d.ts
CHANGED
|
@@ -20,6 +20,23 @@ export interface SendA2AResponseParams {
|
|
|
20
20
|
* Send an A2A artifact update response.
|
|
21
21
|
*/
|
|
22
22
|
export declare function sendA2AResponse(params: SendA2AResponseParams): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Parameters for sending a reasoning text update (intermediate, streamed).
|
|
25
|
+
*/
|
|
26
|
+
export interface SendReasoningTextUpdateParams {
|
|
27
|
+
config: XYChannelConfig;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
taskId: string;
|
|
30
|
+
messageId: string;
|
|
31
|
+
text: string;
|
|
32
|
+
append?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Send an A2A artifact-update with reasoningText part.
|
|
36
|
+
* Used for onToolStart, onToolResult, onReasoningStream, onReasoningEnd, onPartialReply.
|
|
37
|
+
* append=true, final=false, lastChunk=true, text is suffixed with newline for markdown rendering.
|
|
38
|
+
*/
|
|
39
|
+
export declare function sendReasoningTextUpdate(params: SendReasoningTextUpdateParams): Promise<void>;
|
|
23
40
|
/**
|
|
24
41
|
* Parameters for sending a status update.
|
|
25
42
|
*/
|
package/dist/src/formatter.js
CHANGED
|
@@ -67,6 +67,50 @@ export async function sendA2AResponse(params) {
|
|
|
67
67
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
68
68
|
log(`[A2A_RESPONSE] β
Message sent successfully`);
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Send an A2A artifact-update with reasoningText part.
|
|
72
|
+
* Used for onToolStart, onToolResult, onReasoningStream, onReasoningEnd, onPartialReply.
|
|
73
|
+
* append=true, final=false, lastChunk=true, text is suffixed with newline for markdown rendering.
|
|
74
|
+
*/
|
|
75
|
+
export async function sendReasoningTextUpdate(params) {
|
|
76
|
+
const { config, sessionId, taskId, messageId, text, append = true } = params;
|
|
77
|
+
const runtime = getXYRuntime();
|
|
78
|
+
const log = runtime?.log ?? console.log;
|
|
79
|
+
const error = runtime?.error ?? console.error;
|
|
80
|
+
const artifact = {
|
|
81
|
+
taskId,
|
|
82
|
+
kind: "artifact-update",
|
|
83
|
+
append,
|
|
84
|
+
lastChunk: true,
|
|
85
|
+
final: false,
|
|
86
|
+
artifact: {
|
|
87
|
+
artifactId: uuidv4(),
|
|
88
|
+
parts: [
|
|
89
|
+
{
|
|
90
|
+
kind: "reasoningText",
|
|
91
|
+
reasoningText: text,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
const jsonRpcResponse = {
|
|
97
|
+
jsonrpc: "2.0",
|
|
98
|
+
id: messageId,
|
|
99
|
+
result: artifact,
|
|
100
|
+
};
|
|
101
|
+
const wsManager = getXYWebSocketManager(config);
|
|
102
|
+
const outboundMessage = {
|
|
103
|
+
msgType: "agent_response",
|
|
104
|
+
agentId: config.agentId,
|
|
105
|
+
sessionId,
|
|
106
|
+
taskId,
|
|
107
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
108
|
+
};
|
|
109
|
+
log(`[REASONING_TEXT] π€ Sending reasoningText update: sessionId=${sessionId}, taskId=${taskId}, text.length=${text.length}`);
|
|
110
|
+
log(JSON.stringify(outboundMessage, null, 2));
|
|
111
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
112
|
+
log(`[REASONING_TEXT] β
Sent successfully`);
|
|
113
|
+
}
|
|
70
114
|
/**
|
|
71
115
|
* Send an A2A task status update.
|
|
72
116
|
* Follows A2A protocol standard format with nested status object.
|
|
@@ -132,6 +176,7 @@ export async function sendCommand(params) {
|
|
|
132
176
|
const log = runtime?.log ?? console.log;
|
|
133
177
|
const error = runtime?.error ?? console.error;
|
|
134
178
|
// Build artifact update with command as data
|
|
179
|
+
// Wrap command in commands array as per protocol requirement
|
|
135
180
|
const artifact = {
|
|
136
181
|
taskId,
|
|
137
182
|
kind: "artifact-update",
|
|
@@ -143,7 +188,9 @@ export async function sendCommand(params) {
|
|
|
143
188
|
parts: [
|
|
144
189
|
{
|
|
145
190
|
kind: "data",
|
|
146
|
-
data:
|
|
191
|
+
data: {
|
|
192
|
+
commands: [command],
|
|
193
|
+
},
|
|
147
194
|
},
|
|
148
195
|
],
|
|
149
196
|
},
|
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(() => {
|
|
@@ -67,9 +72,12 @@ export class HeartbeatManager {
|
|
|
67
72
|
}
|
|
68
73
|
try {
|
|
69
74
|
// Send application-level heartbeat message
|
|
75
|
+
console.log(`[WS-${this.serverName}-SEND] Sending heartbeat frame:`, this.config.message);
|
|
70
76
|
this.ws.send(this.config.message);
|
|
77
|
+
console.log(`[WS-${this.serverName}-SEND] Heartbeat message sent, size: ${this.config.message.length} bytes`);
|
|
71
78
|
// Send protocol-level ping
|
|
72
79
|
this.ws.ping();
|
|
80
|
+
console.log(`[WS-${this.serverName}-SEND] Protocol-level ping sent`);
|
|
73
81
|
// Setup timeout timer
|
|
74
82
|
this.timeoutTimer = setTimeout(() => {
|
|
75
83
|
this.error(`Heartbeat timeout for ${this.serverName}`);
|
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.
|