@ynhcj/xiaoyi-channel 0.0.5 โ 0.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/bot.js +23 -7
- package/dist/src/channel.js +3 -1
- package/dist/src/formatter.js +37 -3
- package/dist/src/monitor.js +26 -19
- package/dist/src/onboarding.js +7 -7
- package/dist/src/outbound.js +28 -5
- package/dist/src/reply-dispatcher.js +16 -18
- package/dist/src/tools/location-tool.js +32 -3
- package/dist/src/tools/note-tool.d.ts +5 -0
- package/dist/src/tools/note-tool.js +130 -0
- package/dist/src/tools/search-note-tool.d.ts +5 -0
- package/dist/src/tools/search-note-tool.js +130 -0
- package/dist/src/tools/session-manager.js +36 -4
- package/dist/src/websocket.js +3 -0
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -19,7 +19,8 @@ export async function handleXYMessage(params) {
|
|
|
19
19
|
try {
|
|
20
20
|
// Check for special messages BEFORE parsing (these have different param structures)
|
|
21
21
|
const messageMethod = message.method;
|
|
22
|
-
log(`[
|
|
22
|
+
log(`[BOT-ENTRY] <<<<<<< Received message with method: ${messageMethod}, id: ${message.id} >>>>>>>`);
|
|
23
|
+
log(`[BOT-ENTRY] Stack trace for debugging:`, new Error().stack?.split('\n').slice(1, 4).join('\n'));
|
|
23
24
|
// Handle clearContext messages (params only has sessionId)
|
|
24
25
|
if (messageMethod === "clearContext" || messageMethod === "clear_context") {
|
|
25
26
|
const sessionId = message.params?.sessionId;
|
|
@@ -61,7 +62,7 @@ export async function handleXYMessage(params) {
|
|
|
61
62
|
// Use sessionId as peer.id to ensure all messages in the same session share context
|
|
62
63
|
let route = core.channel.routing.resolveAgentRoute({
|
|
63
64
|
cfg,
|
|
64
|
-
channel: "
|
|
65
|
+
channel: "xiaoyi-channel",
|
|
65
66
|
accountId, // "default"
|
|
66
67
|
peer: {
|
|
67
68
|
kind: "direct",
|
|
@@ -70,6 +71,10 @@ export async function handleXYMessage(params) {
|
|
|
70
71
|
});
|
|
71
72
|
log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
|
|
72
73
|
// Register session context for tools
|
|
74
|
+
log(`[BOT] ๐ About to register session for tools...`);
|
|
75
|
+
log(`[BOT] - sessionKey: ${route.sessionKey}`);
|
|
76
|
+
log(`[BOT] - sessionId: ${parsed.sessionId}`);
|
|
77
|
+
log(`[BOT] - taskId: ${parsed.taskId}`);
|
|
73
78
|
registerSession(route.sessionKey, {
|
|
74
79
|
config,
|
|
75
80
|
sessionId: parsed.sessionId,
|
|
@@ -77,6 +82,7 @@ export async function handleXYMessage(params) {
|
|
|
77
82
|
messageId: parsed.messageId,
|
|
78
83
|
agentId: route.accountId,
|
|
79
84
|
});
|
|
85
|
+
log(`[BOT] โ
Session registered for tools`);
|
|
80
86
|
// Extract text and files from parts
|
|
81
87
|
const text = extractTextFromParts(parsed.parts);
|
|
82
88
|
const fileParts = extractFileParts(parsed.parts);
|
|
@@ -113,13 +119,13 @@ export async function handleXYMessage(params) {
|
|
|
113
119
|
GroupSubject: undefined,
|
|
114
120
|
SenderName: parsed.sessionId,
|
|
115
121
|
SenderId: parsed.sessionId,
|
|
116
|
-
Provider: "
|
|
117
|
-
Surface: "
|
|
122
|
+
Provider: "xiaoyi-channel",
|
|
123
|
+
Surface: "xiaoyi-channel",
|
|
118
124
|
MessageSid: parsed.messageId,
|
|
119
125
|
Timestamp: Date.now(),
|
|
120
126
|
WasMentioned: false,
|
|
121
127
|
CommandAuthorized: true,
|
|
122
|
-
OriginatingChannel: "
|
|
128
|
+
OriginatingChannel: "xiaoyi-channel",
|
|
123
129
|
OriginatingTo: parsed.sessionId, // Original message target
|
|
124
130
|
ReplyToBody: undefined, // A2A protocol doesn't support reply/quote
|
|
125
131
|
...mediaPayload,
|
|
@@ -150,12 +156,16 @@ export async function handleXYMessage(params) {
|
|
|
150
156
|
startStatusInterval();
|
|
151
157
|
log(`xy: dispatching to agent (session=${parsed.sessionId})`);
|
|
152
158
|
// Dispatch to OpenClaw core using correct API (following feishu pattern)
|
|
159
|
+
log(`[BOT] ๐ Starting dispatcher with session: ${route.sessionKey}`);
|
|
153
160
|
await core.channel.reply.withReplyDispatcher({
|
|
154
161
|
dispatcher,
|
|
155
162
|
onSettled: () => {
|
|
163
|
+
log(`[BOT] ๐ onSettled called for session: ${route.sessionKey}`);
|
|
164
|
+
log(`[BOT] - About to unregister session...`);
|
|
156
165
|
markDispatchIdle();
|
|
157
166
|
// Unregister session context when done
|
|
158
167
|
unregisterSession(route.sessionKey);
|
|
168
|
+
log(`[BOT] โ
Session unregistered in onSettled`);
|
|
159
169
|
},
|
|
160
170
|
run: () => core.channel.reply.dispatchReplyFromConfig({
|
|
161
171
|
ctx: ctxPayload,
|
|
@@ -164,30 +174,36 @@ export async function handleXYMessage(params) {
|
|
|
164
174
|
replyOptions,
|
|
165
175
|
}),
|
|
166
176
|
});
|
|
177
|
+
log(`[BOT] โ
Dispatcher completed for session: ${parsed.sessionId}`);
|
|
167
178
|
log(`xy: dispatch complete (session=${parsed.sessionId})`);
|
|
168
179
|
}
|
|
169
180
|
catch (err) {
|
|
170
181
|
error("Failed to handle XY message:", err);
|
|
171
182
|
runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
|
|
183
|
+
log(`[BOT] โ Error occurred, attempting cleanup...`);
|
|
172
184
|
// Try to unregister session on error (if route was established)
|
|
173
185
|
try {
|
|
174
186
|
const core = getXYRuntime();
|
|
175
187
|
const params = message.params;
|
|
176
188
|
const sessionId = params?.sessionId;
|
|
177
189
|
if (sessionId) {
|
|
190
|
+
log(`[BOT] ๐งน Cleaning up session after error: ${sessionId}`);
|
|
178
191
|
const route = core.channel.routing.resolveAgentRoute({
|
|
179
192
|
cfg,
|
|
180
|
-
channel: "
|
|
193
|
+
channel: "xiaoyi-channel",
|
|
181
194
|
accountId,
|
|
182
195
|
peer: {
|
|
183
196
|
kind: "direct",
|
|
184
197
|
id: sessionId, // โ
Use sessionId for cleanup consistency
|
|
185
198
|
},
|
|
186
199
|
});
|
|
200
|
+
log(`[BOT] - Unregistering session: ${route.sessionKey}`);
|
|
187
201
|
unregisterSession(route.sessionKey);
|
|
202
|
+
log(`[BOT] โ
Session unregistered after error`);
|
|
188
203
|
}
|
|
189
204
|
}
|
|
190
|
-
catch {
|
|
205
|
+
catch (cleanupErr) {
|
|
206
|
+
log(`[BOT] โ ๏ธ Cleanup failed:`, cleanupErr);
|
|
191
207
|
// Ignore cleanup errors
|
|
192
208
|
}
|
|
193
209
|
throw err;
|
package/dist/src/channel.js
CHANGED
|
@@ -3,6 +3,8 @@ import { xyConfigSchema } from "./config-schema.js";
|
|
|
3
3
|
import { xyOutbound } from "./outbound.js";
|
|
4
4
|
import { xyOnboardingAdapter } from "./onboarding.js";
|
|
5
5
|
import { locationTool } from "./tools/location-tool.js";
|
|
6
|
+
import { noteTool } from "./tools/note-tool.js";
|
|
7
|
+
import { searchNoteTool } from "./tools/search-note-tool.js";
|
|
6
8
|
/**
|
|
7
9
|
* Xiaoyi Channel Plugin for OpenClaw.
|
|
8
10
|
* Implements Xiaoyi A2A protocol with dual WebSocket connections.
|
|
@@ -41,7 +43,7 @@ export const xyPlugin = {
|
|
|
41
43
|
},
|
|
42
44
|
outbound: xyOutbound,
|
|
43
45
|
onboarding: xyOnboardingAdapter,
|
|
44
|
-
agentTools: [locationTool],
|
|
46
|
+
agentTools: [locationTool, noteTool, searchNoteTool],
|
|
45
47
|
messaging: {
|
|
46
48
|
normalizeTarget: (raw) => {
|
|
47
49
|
const trimmed = raw.trim();
|
package/dist/src/formatter.js
CHANGED
|
@@ -51,8 +51,21 @@ export async function sendA2AResponse(params) {
|
|
|
51
51
|
taskId,
|
|
52
52
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
53
53
|
};
|
|
54
|
+
// ๐ Log complete response body
|
|
55
|
+
log(`[A2A_RESPONSE] ๐ค Sending A2A artifact-update response:`);
|
|
56
|
+
log(`[A2A_RESPONSE] - sessionId: ${sessionId}`);
|
|
57
|
+
log(`[A2A_RESPONSE] - taskId: ${taskId}`);
|
|
58
|
+
log(`[A2A_RESPONSE] - messageId: ${messageId}`);
|
|
59
|
+
log(`[A2A_RESPONSE] - append: ${append}`);
|
|
60
|
+
log(`[A2A_RESPONSE] - final: ${final}`);
|
|
61
|
+
log(`[A2A_RESPONSE] - text length: ${text?.length ?? 0}`);
|
|
62
|
+
log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
|
|
63
|
+
log(`[A2A_RESPONSE] ๐ฆ Complete outbound message:`);
|
|
64
|
+
log(JSON.stringify(outboundMessage, null, 2));
|
|
65
|
+
log(`[A2A_RESPONSE] ๐ฆ JSON-RPC response body:`);
|
|
66
|
+
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
54
67
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
55
|
-
log(`
|
|
68
|
+
log(`[A2A_RESPONSE] โ
Message sent successfully`);
|
|
56
69
|
}
|
|
57
70
|
/**
|
|
58
71
|
* Send an A2A task status update.
|
|
@@ -96,8 +109,19 @@ export async function sendStatusUpdate(params) {
|
|
|
96
109
|
taskId,
|
|
97
110
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
98
111
|
};
|
|
112
|
+
// ๐ Log complete response body
|
|
113
|
+
log(`[A2A_STATUS] ๐ค Sending A2A status-update:`);
|
|
114
|
+
log(`[A2A_STATUS] - sessionId: ${sessionId}`);
|
|
115
|
+
log(`[A2A_STATUS] - taskId: ${taskId}`);
|
|
116
|
+
log(`[A2A_STATUS] - messageId: ${messageId}`);
|
|
117
|
+
log(`[A2A_STATUS] - state: ${state}`);
|
|
118
|
+
log(`[A2A_STATUS] - text: "${text}"`);
|
|
119
|
+
log(`[A2A_STATUS] ๐ฆ Complete outbound message:`);
|
|
120
|
+
log(JSON.stringify(outboundMessage, null, 2));
|
|
121
|
+
log(`[A2A_STATUS] ๐ฆ JSON-RPC response body:`);
|
|
122
|
+
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
99
123
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
100
|
-
log(`
|
|
124
|
+
log(`[A2A_STATUS] โ
Status update sent successfully`);
|
|
101
125
|
}
|
|
102
126
|
/**
|
|
103
127
|
* Send a command as an artifact update (final=false).
|
|
@@ -139,8 +163,18 @@ export async function sendCommand(params) {
|
|
|
139
163
|
taskId,
|
|
140
164
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
141
165
|
};
|
|
166
|
+
// ๐ Log complete response body
|
|
167
|
+
log(`[A2A_COMMAND] ๐ค Sending A2A command:`);
|
|
168
|
+
log(`[A2A_COMMAND] - sessionId: ${sessionId}`);
|
|
169
|
+
log(`[A2A_COMMAND] - taskId: ${taskId}`);
|
|
170
|
+
log(`[A2A_COMMAND] - messageId: ${messageId}`);
|
|
171
|
+
log(`[A2A_COMMAND] - command: ${command.header.namespace}::${command.header.name}`);
|
|
172
|
+
log(`[A2A_COMMAND] ๐ฆ Complete outbound message:`);
|
|
173
|
+
log(JSON.stringify(outboundMessage, null, 2));
|
|
174
|
+
log(`[A2A_COMMAND] ๐ฆ JSON-RPC response body:`);
|
|
175
|
+
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
142
176
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
143
|
-
log(`
|
|
177
|
+
log(`[A2A_COMMAND] โ
Command sent successfully`);
|
|
144
178
|
}
|
|
145
179
|
/**
|
|
146
180
|
* Send a clearContext response.
|
package/dist/src/monitor.js
CHANGED
|
@@ -44,25 +44,9 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
44
44
|
// Create session queue for ordered message processing
|
|
45
45
|
const enqueue = createSessionQueue();
|
|
46
46
|
return new Promise((resolve, reject) => {
|
|
47
|
-
|
|
48
|
-
log("XY gateway: cleaning up...");
|
|
49
|
-
wsManager.disconnect();
|
|
50
|
-
loggedServers.clear();
|
|
51
|
-
};
|
|
52
|
-
const handleAbort = () => {
|
|
53
|
-
log("XY gateway: abort signal received, stopping");
|
|
54
|
-
cleanup();
|
|
55
|
-
log("XY gateway stopped");
|
|
56
|
-
resolve();
|
|
57
|
-
};
|
|
58
|
-
if (opts.abortSignal?.aborted) {
|
|
59
|
-
cleanup();
|
|
60
|
-
resolve();
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
opts.abortSignal?.addEventListener("abort", handleAbort, { once: true });
|
|
64
|
-
// Setup event handlers
|
|
47
|
+
// Event handlers (defined early so they can be referenced in cleanup)
|
|
65
48
|
const messageHandler = (message, sessionId, serverId) => {
|
|
49
|
+
log(`[MONITOR-HANDLER] ####### messageHandler triggered: serverId=${serverId}, sessionId=${sessionId}, messageId=${message.id} #######`);
|
|
66
50
|
const task = async () => {
|
|
67
51
|
try {
|
|
68
52
|
await handleXYMessage({
|
|
@@ -95,7 +79,30 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
95
79
|
const errorHandler = (err, serverId) => {
|
|
96
80
|
error(`XY gateway: ${serverId} error: ${String(err)}`);
|
|
97
81
|
};
|
|
98
|
-
|
|
82
|
+
const cleanup = () => {
|
|
83
|
+
log("XY gateway: cleaning up...");
|
|
84
|
+
// Remove event handlers to prevent duplicate calls on gateway restart
|
|
85
|
+
wsManager.off("message", messageHandler);
|
|
86
|
+
wsManager.off("connected", connectedHandler);
|
|
87
|
+
wsManager.off("disconnected", disconnectedHandler);
|
|
88
|
+
wsManager.off("error", errorHandler);
|
|
89
|
+
// Don't disconnect the shared wsManager as it may be used elsewhere
|
|
90
|
+
// wsManager.disconnect();
|
|
91
|
+
loggedServers.clear();
|
|
92
|
+
};
|
|
93
|
+
const handleAbort = () => {
|
|
94
|
+
log("XY gateway: abort signal received, stopping");
|
|
95
|
+
cleanup();
|
|
96
|
+
log("XY gateway stopped");
|
|
97
|
+
resolve();
|
|
98
|
+
};
|
|
99
|
+
if (opts.abortSignal?.aborted) {
|
|
100
|
+
cleanup();
|
|
101
|
+
resolve();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
opts.abortSignal?.addEventListener("abort", handleAbort, { once: true });
|
|
105
|
+
// Register event handlers (handlers are defined above in cleanup scope)
|
|
99
106
|
wsManager.on("message", messageHandler);
|
|
100
107
|
wsManager.on("connected", connectedHandler);
|
|
101
108
|
wsManager.on("disconnected", disconnectedHandler);
|
package/dist/src/onboarding.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const channel = "
|
|
1
|
+
const channel = "xiaoyi-channel";
|
|
2
2
|
/**
|
|
3
3
|
* Check if XY channel is properly configured with required fields
|
|
4
4
|
*/
|
|
5
5
|
function isXYConfigured(cfg) {
|
|
6
6
|
try {
|
|
7
|
-
const xyConfig = cfg.channels?.
|
|
7
|
+
const xyConfig = cfg.channels?.["xiaoyi-channel"];
|
|
8
8
|
if (!xyConfig) {
|
|
9
9
|
return false;
|
|
10
10
|
}
|
|
@@ -26,7 +26,7 @@ function isXYConfigured(cfg) {
|
|
|
26
26
|
*/
|
|
27
27
|
async function getStatus({ cfg }) {
|
|
28
28
|
const configured = isXYConfigured(cfg);
|
|
29
|
-
const xyConfig = cfg.channels?.
|
|
29
|
+
const xyConfig = cfg.channels?.["xiaoyi-channel"];
|
|
30
30
|
const statusLines = [];
|
|
31
31
|
if (configured) {
|
|
32
32
|
const wsUrl1 = xyConfig?.wsUrl1 || "ws://localhost:8765/ws/link";
|
|
@@ -49,7 +49,7 @@ async function getStatus({ cfg }) {
|
|
|
49
49
|
*/
|
|
50
50
|
async function configure({ cfg, prompter, }) {
|
|
51
51
|
// Note current configuration status
|
|
52
|
-
const currentConfig = cfg.channels?.
|
|
52
|
+
const currentConfig = cfg.channels?.["xiaoyi-channel"];
|
|
53
53
|
const isUpdate = Boolean(currentConfig);
|
|
54
54
|
await prompter.note([
|
|
55
55
|
"XY Channel - ๅฐ่บ A2A ๅ่ฎฎ้
็ฝฎ",
|
|
@@ -117,7 +117,7 @@ async function configure({ cfg, prompter, }) {
|
|
|
117
117
|
...cfg,
|
|
118
118
|
channels: {
|
|
119
119
|
...cfg.channels,
|
|
120
|
-
|
|
120
|
+
"xiaoyi-channel": {
|
|
121
121
|
enabled: true,
|
|
122
122
|
wsUrl1: wsUrl1.trim(),
|
|
123
123
|
wsUrl2: wsUrl2.trim(),
|
|
@@ -164,8 +164,8 @@ export const xyOnboardingAdapter = {
|
|
|
164
164
|
...cfg,
|
|
165
165
|
channels: {
|
|
166
166
|
...cfg.channels,
|
|
167
|
-
|
|
168
|
-
...(cfg.channels?.
|
|
167
|
+
"xiaoyi-channel": {
|
|
168
|
+
...(cfg.channels?.["xiaoyi-channel"] || {}),
|
|
169
169
|
enabled: false,
|
|
170
170
|
},
|
|
171
171
|
},
|
package/dist/src/outbound.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveXYConfig } from "./config.js";
|
|
2
2
|
import { XYFileUploadService } from "./file-upload.js";
|
|
3
3
|
import { XYPushService } from "./push.js";
|
|
4
|
+
import { getLatestSessionContext } from "./tools/session-manager.js";
|
|
4
5
|
// Special marker for default push delivery when no target is specified
|
|
5
6
|
const DEFAULT_PUSH_MARKER = "default";
|
|
6
7
|
/**
|
|
@@ -14,6 +15,9 @@ export const xyOutbound = {
|
|
|
14
15
|
* Resolve delivery target for XY channel.
|
|
15
16
|
* When no target is specified (e.g., in cron jobs with announce mode),
|
|
16
17
|
* returns a default marker that will be handled by sendText.
|
|
18
|
+
*
|
|
19
|
+
* For message tool calls, if only sessionId is provided, it will look up
|
|
20
|
+
* the active session context to construct the full "sessionId::taskId" format.
|
|
17
21
|
*/
|
|
18
22
|
resolveTarget: ({ cfg, to, accountId, mode }) => {
|
|
19
23
|
// If no target provided, use default marker for push delivery
|
|
@@ -24,11 +28,30 @@ export const xyOutbound = {
|
|
|
24
28
|
to: DEFAULT_PUSH_MARKER,
|
|
25
29
|
};
|
|
26
30
|
}
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
const trimmedTo = to.trim();
|
|
32
|
+
// If the target doesn't contain "::", try to enhance it with taskId from session context
|
|
33
|
+
if (!trimmedTo.includes("::")) {
|
|
34
|
+
console.log(`[xyOutbound.resolveTarget] Target "${trimmedTo}" missing taskId, looking up session context`);
|
|
35
|
+
// Try to get the latest session context
|
|
36
|
+
const sessionContext = getLatestSessionContext();
|
|
37
|
+
if (sessionContext && sessionContext.sessionId === trimmedTo) {
|
|
38
|
+
const enhancedTarget = `${trimmedTo}::${sessionContext.taskId}`;
|
|
39
|
+
console.log(`[xyOutbound.resolveTarget] Enhanced target: ${enhancedTarget}`);
|
|
40
|
+
return {
|
|
41
|
+
ok: true,
|
|
42
|
+
to: enhancedTarget,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.log(`[xyOutbound.resolveTarget] Could not find matching session context for "${trimmedTo}"`);
|
|
47
|
+
// Still return the original target, but it may fail in sendMedia
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Otherwise, use the provided target (either already in correct format or for sendText)
|
|
51
|
+
console.log(`[xyOutbound.resolveTarget] Using provided target:`, trimmedTo);
|
|
29
52
|
return {
|
|
30
53
|
ok: true,
|
|
31
|
-
to:
|
|
54
|
+
to: trimmedTo,
|
|
32
55
|
};
|
|
33
56
|
},
|
|
34
57
|
sendText: async ({ cfg, to, text, accountId }) => {
|
|
@@ -58,7 +81,7 @@ export const xyOutbound = {
|
|
|
58
81
|
console.log(`[xyOutbound.sendText] Completed successfully`);
|
|
59
82
|
// Return message info
|
|
60
83
|
return {
|
|
61
|
-
channel: "
|
|
84
|
+
channel: "xiaoyi-channel",
|
|
62
85
|
messageId: Date.now().toString(),
|
|
63
86
|
chatId: actualTo,
|
|
64
87
|
};
|
|
@@ -135,7 +158,7 @@ export const xyOutbound = {
|
|
|
135
158
|
console.log(`[xyOutbound.sendMedia] WebSocket message sent successfully`);
|
|
136
159
|
// Return message info
|
|
137
160
|
return {
|
|
138
|
-
channel: "
|
|
161
|
+
channel: "xiaoyi-channel",
|
|
139
162
|
messageId: fileId,
|
|
140
163
|
chatId: to,
|
|
141
164
|
};
|
|
@@ -11,6 +11,11 @@ export function createXYReplyDispatcher(params) {
|
|
|
11
11
|
const { cfg, runtime, sessionId, taskId, messageId, accountId } = params;
|
|
12
12
|
const log = runtime?.log ?? console.log;
|
|
13
13
|
const error = runtime?.error ?? console.error;
|
|
14
|
+
log(`[DISPATCHER-CREATE] ******* Creating dispatcher for session=${sessionId}, taskId=${taskId}, messageId=${messageId} *******`);
|
|
15
|
+
log(`[DISPATCHER-CREATE] Stack trace:`, new Error().stack?.split('\n').slice(1, 4).join('\n'));
|
|
16
|
+
log(`[DISPATCHER-CREATE] ======== Creating reply dispatcher ========`);
|
|
17
|
+
log(`[DISPATCHER-CREATE] sessionId: ${sessionId}, taskId: ${taskId}, messageId: ${messageId}`);
|
|
18
|
+
log(`[DISPATCHER-CREATE] Stack trace:`, new Error().stack?.split('\n').slice(1, 4).join('\n'));
|
|
14
19
|
// Get runtime (already validated in monitor.ts, but get reference for use)
|
|
15
20
|
const core = getXYRuntime();
|
|
16
21
|
// Resolve configuration
|
|
@@ -23,6 +28,8 @@ export function createXYReplyDispatcher(params) {
|
|
|
23
28
|
let hasSentResponse = false;
|
|
24
29
|
// Track if we've sent the final empty message
|
|
25
30
|
let finalSent = false;
|
|
31
|
+
// Accumulate all text from deliver calls
|
|
32
|
+
let accumulatedText = "";
|
|
26
33
|
/**
|
|
27
34
|
* Start the status update interval
|
|
28
35
|
* Call this immediately after creating the dispatcher
|
|
@@ -76,19 +83,10 @@ export function createXYReplyDispatcher(params) {
|
|
|
76
83
|
log(`[DELIVER SKIP] Empty text, skipping`);
|
|
77
84
|
return;
|
|
78
85
|
}
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
await sendA2AResponse({
|
|
82
|
-
config,
|
|
83
|
-
sessionId,
|
|
84
|
-
taskId,
|
|
85
|
-
messageId,
|
|
86
|
-
text: text,
|
|
87
|
-
append: true,
|
|
88
|
-
final: false,
|
|
89
|
-
});
|
|
86
|
+
// Accumulate text instead of sending immediately
|
|
87
|
+
accumulatedText += text;
|
|
90
88
|
hasSentResponse = true;
|
|
91
|
-
log(`[DELIVER
|
|
89
|
+
log(`[DELIVER ACCUMULATE] Accumulated text, current length=${accumulatedText.length}`);
|
|
92
90
|
}
|
|
93
91
|
catch (deliverError) {
|
|
94
92
|
error(`Failed to deliver message:`, deliverError);
|
|
@@ -117,24 +115,24 @@ export function createXYReplyDispatcher(params) {
|
|
|
117
115
|
},
|
|
118
116
|
onIdle: async () => {
|
|
119
117
|
log(`[ON_IDLE] Reply idle for session ${sessionId}, hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
|
|
120
|
-
// Send
|
|
118
|
+
// Send accumulated text with append=false and final=true
|
|
121
119
|
if (hasSentResponse && !finalSent) {
|
|
122
|
-
log(`[ON_IDLE] Sending
|
|
120
|
+
log(`[ON_IDLE] Sending accumulated text, length=${accumulatedText.length}`);
|
|
123
121
|
try {
|
|
124
122
|
await sendA2AResponse({
|
|
125
123
|
config,
|
|
126
124
|
sessionId,
|
|
127
125
|
taskId,
|
|
128
126
|
messageId,
|
|
129
|
-
text:
|
|
130
|
-
append:
|
|
127
|
+
text: accumulatedText,
|
|
128
|
+
append: false,
|
|
131
129
|
final: true,
|
|
132
130
|
});
|
|
133
131
|
finalSent = true;
|
|
134
|
-
log(`[ON_IDLE] Sent
|
|
132
|
+
log(`[ON_IDLE] Sent accumulated text`);
|
|
135
133
|
}
|
|
136
134
|
catch (err) {
|
|
137
|
-
error(`[ON_IDLE] Failed to send
|
|
135
|
+
error(`[ON_IDLE] Failed to send accumulated text:`, err);
|
|
138
136
|
}
|
|
139
137
|
}
|
|
140
138
|
else {
|
|
@@ -16,16 +16,31 @@ export const locationTool = {
|
|
|
16
16
|
required: [],
|
|
17
17
|
},
|
|
18
18
|
async execute(toolCallId, params) {
|
|
19
|
-
logger.
|
|
19
|
+
logger.log(`[LOCATION_TOOL] ๐ Starting execution`);
|
|
20
|
+
logger.log(`[LOCATION_TOOL] - toolCallId: ${toolCallId}`);
|
|
21
|
+
logger.log(`[LOCATION_TOOL] - params:`, JSON.stringify(params));
|
|
22
|
+
logger.log(`[LOCATION_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
20
23
|
// Get session context
|
|
24
|
+
logger.log(`[LOCATION_TOOL] ๐ Attempting to get session context...`);
|
|
21
25
|
const sessionContext = getLatestSessionContext();
|
|
22
26
|
if (!sessionContext) {
|
|
27
|
+
logger.error(`[LOCATION_TOOL] โ FAILED: No active session found!`);
|
|
28
|
+
logger.error(`[LOCATION_TOOL] - toolCallId: ${toolCallId}`);
|
|
29
|
+
logger.error(`[LOCATION_TOOL] - This suggests the session was not registered or already cleaned up`);
|
|
23
30
|
throw new Error("No active XY session found. Location tool can only be used during an active conversation.");
|
|
24
31
|
}
|
|
32
|
+
logger.log(`[LOCATION_TOOL] โ
Session context found`);
|
|
33
|
+
logger.log(`[LOCATION_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
34
|
+
logger.log(`[LOCATION_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
35
|
+
logger.log(`[LOCATION_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
36
|
+
logger.log(`[LOCATION_TOOL] - agentId: ${sessionContext.agentId}`);
|
|
25
37
|
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
26
38
|
// Get WebSocket manager
|
|
39
|
+
logger.log(`[LOCATION_TOOL] ๐ Getting WebSocket manager...`);
|
|
27
40
|
const wsManager = getXYWebSocketManager(config);
|
|
41
|
+
logger.log(`[LOCATION_TOOL] โ
WebSocket manager obtained`);
|
|
28
42
|
// Build GetCurrentLocation command
|
|
43
|
+
logger.log(`[LOCATION_TOOL] ๐ฆ Building GetCurrentLocation command...`);
|
|
29
44
|
const command = {
|
|
30
45
|
header: {
|
|
31
46
|
namespace: "Common",
|
|
@@ -45,20 +60,27 @@ export const locationTool = {
|
|
|
45
60
|
},
|
|
46
61
|
};
|
|
47
62
|
// Send command and wait for response (5 second timeout)
|
|
63
|
+
logger.log(`[LOCATION_TOOL] โณ Setting up promise to wait for location response...`);
|
|
64
|
+
logger.log(`[LOCATION_TOOL] - Timeout: 5 seconds`);
|
|
48
65
|
return new Promise((resolve, reject) => {
|
|
49
66
|
const timeout = setTimeout(() => {
|
|
67
|
+
logger.error(`[LOCATION_TOOL] โฐ Timeout: No response received within 5 seconds`);
|
|
50
68
|
wsManager.off("data-event", handler);
|
|
51
69
|
reject(new Error("่ทๅไฝ็ฝฎ่ถ
ๆถ๏ผ5็ง๏ผ"));
|
|
52
70
|
}, 5000);
|
|
53
71
|
// Listen for data events from WebSocket
|
|
54
72
|
const handler = (event) => {
|
|
55
|
-
logger.
|
|
73
|
+
logger.log(`[LOCATION_TOOL] ๐จ Received data event:`, JSON.stringify(event));
|
|
56
74
|
if (event.intentName === "GetCurrentLocation") {
|
|
75
|
+
logger.log(`[LOCATION_TOOL] ๐ฏ GetCurrentLocation event received`);
|
|
76
|
+
logger.log(`[LOCATION_TOOL] - status: ${event.status}`);
|
|
57
77
|
clearTimeout(timeout);
|
|
58
78
|
wsManager.off("data-event", handler);
|
|
59
79
|
if (event.status === "success" && event.outputs) {
|
|
60
80
|
const { latitude, longitude } = event.outputs;
|
|
61
|
-
logger.log(`Location retrieved
|
|
81
|
+
logger.log(`[LOCATION_TOOL] โ
Location retrieved successfully`);
|
|
82
|
+
logger.log(`[LOCATION_TOOL] - latitude: ${latitude}`);
|
|
83
|
+
logger.log(`[LOCATION_TOOL] - longitude: ${longitude}`);
|
|
62
84
|
resolve({
|
|
63
85
|
content: [
|
|
64
86
|
{
|
|
@@ -73,21 +95,28 @@ export const locationTool = {
|
|
|
73
95
|
});
|
|
74
96
|
}
|
|
75
97
|
else {
|
|
98
|
+
logger.error(`[LOCATION_TOOL] โ Location retrieval failed`);
|
|
99
|
+
logger.error(`[LOCATION_TOOL] - status: ${event.status}`);
|
|
76
100
|
reject(new Error(`่ทๅไฝ็ฝฎๅคฑ่ดฅ: ${event.status}`));
|
|
77
101
|
}
|
|
78
102
|
}
|
|
79
103
|
};
|
|
80
104
|
// Register event handler
|
|
81
105
|
// Note: The WebSocket manager needs to emit 'data-event' when receiving data events
|
|
106
|
+
logger.log(`[LOCATION_TOOL] ๐ก Registering data-event handler on WebSocket manager`);
|
|
82
107
|
wsManager.on("data-event", handler);
|
|
83
108
|
// Send the command
|
|
109
|
+
logger.log(`[LOCATION_TOOL] ๐ค Sending GetCurrentLocation command...`);
|
|
84
110
|
sendCommand({
|
|
85
111
|
config,
|
|
86
112
|
sessionId,
|
|
87
113
|
taskId,
|
|
88
114
|
messageId,
|
|
89
115
|
command,
|
|
116
|
+
}).then(() => {
|
|
117
|
+
logger.log(`[LOCATION_TOOL] โ
Command sent successfully, waiting for response...`);
|
|
90
118
|
}).catch((error) => {
|
|
119
|
+
logger.error(`[LOCATION_TOOL] โ Failed to send command:`, error);
|
|
91
120
|
clearTimeout(timeout);
|
|
92
121
|
wsManager.off("data-event", handler);
|
|
93
122
|
reject(error);
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getLatestSessionContext } from "./session-manager.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* XY note tool - creates a note on user's device.
|
|
7
|
+
* Requires title and content parameters.
|
|
8
|
+
*/
|
|
9
|
+
export const noteTool = {
|
|
10
|
+
name: "create_note",
|
|
11
|
+
label: "Create Note",
|
|
12
|
+
description: "ๅจ็จๆท่ฎพๅคไธๅๅปบๅคๅฟๅฝใ้่ฆๆไพๅคๅฟๅฝๆ ้ขๅๅ
ๅฎนใ",
|
|
13
|
+
parameters: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
title: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "ๅคๅฟๅฝๆ ้ข",
|
|
19
|
+
},
|
|
20
|
+
content: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "ๅคๅฟๅฝๅ
ๅฎน",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
required: ["title", "content"],
|
|
26
|
+
},
|
|
27
|
+
async execute(toolCallId, params) {
|
|
28
|
+
logger.debug("Executing note tool, toolCallId:", toolCallId);
|
|
29
|
+
// Validate parameters
|
|
30
|
+
if (!params.title || !params.content) {
|
|
31
|
+
throw new Error("Missing required parameters: title and content are required");
|
|
32
|
+
}
|
|
33
|
+
// Get session context
|
|
34
|
+
const sessionContext = getLatestSessionContext();
|
|
35
|
+
if (!sessionContext) {
|
|
36
|
+
throw new Error("No active XY session found. Note tool can only be used during an active conversation.");
|
|
37
|
+
}
|
|
38
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
39
|
+
// Get WebSocket manager
|
|
40
|
+
const wsManager = getXYWebSocketManager(config);
|
|
41
|
+
// Build CreateNote command
|
|
42
|
+
const command = {
|
|
43
|
+
header: {
|
|
44
|
+
namespace: "Common",
|
|
45
|
+
name: "Action",
|
|
46
|
+
},
|
|
47
|
+
payload: {
|
|
48
|
+
cardParam: {},
|
|
49
|
+
executeParam: {
|
|
50
|
+
executeMode: "background",
|
|
51
|
+
intentName: "CreateNote",
|
|
52
|
+
bundleName: "com.huawei.hmos.notepad",
|
|
53
|
+
dimension: "",
|
|
54
|
+
needUnlock: true,
|
|
55
|
+
actionResponse: true,
|
|
56
|
+
timeOut: 5,
|
|
57
|
+
intentParam: {
|
|
58
|
+
title: params.title,
|
|
59
|
+
content: params.content,
|
|
60
|
+
},
|
|
61
|
+
achieveType: "INTENT",
|
|
62
|
+
},
|
|
63
|
+
responses: [
|
|
64
|
+
{
|
|
65
|
+
resultCode: "",
|
|
66
|
+
displayText: "",
|
|
67
|
+
ttsText: "",
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
needUploadResult: true,
|
|
71
|
+
noHalfPage: false,
|
|
72
|
+
pageControlRelated: false,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
// Send command and wait for response (5 second timeout)
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const timeout = setTimeout(() => {
|
|
78
|
+
wsManager.off("data-event", handler);
|
|
79
|
+
reject(new Error("ๅๅปบๅคๅฟๅฝ่ถ
ๆถ๏ผ5็ง๏ผ"));
|
|
80
|
+
}, 5000);
|
|
81
|
+
// Listen for data events from WebSocket
|
|
82
|
+
const handler = (event) => {
|
|
83
|
+
logger.debug("Received data event:", event);
|
|
84
|
+
if (event.intentName === "CreateNote") {
|
|
85
|
+
clearTimeout(timeout);
|
|
86
|
+
wsManager.off("data-event", handler);
|
|
87
|
+
if (event.status === "success" && event.outputs) {
|
|
88
|
+
const { result, code } = event.outputs;
|
|
89
|
+
logger.log(`Note created: title=${result?.title}, id=${result?.entityId}`);
|
|
90
|
+
resolve({
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: JSON.stringify({
|
|
95
|
+
success: true,
|
|
96
|
+
note: {
|
|
97
|
+
entityId: result?.entityId,
|
|
98
|
+
title: result?.title,
|
|
99
|
+
content: result?.content,
|
|
100
|
+
entityName: result?.entityName,
|
|
101
|
+
modifiedDate: result?.modifiedDate,
|
|
102
|
+
},
|
|
103
|
+
code,
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
reject(new Error(`ๅๅปบๅคๅฟๅฝๅคฑ่ดฅ: ${event.status}`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
// Register event handler
|
|
115
|
+
wsManager.on("data-event", handler);
|
|
116
|
+
// Send the command
|
|
117
|
+
sendCommand({
|
|
118
|
+
config,
|
|
119
|
+
sessionId,
|
|
120
|
+
taskId,
|
|
121
|
+
messageId,
|
|
122
|
+
command,
|
|
123
|
+
}).catch((error) => {
|
|
124
|
+
clearTimeout(timeout);
|
|
125
|
+
wsManager.off("data-event", handler);
|
|
126
|
+
reject(error);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getLatestSessionContext } from "./session-manager.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* XY search note tool - searches notes on user's device.
|
|
7
|
+
* Returns matching notes based on query string.
|
|
8
|
+
*/
|
|
9
|
+
export const searchNoteTool = {
|
|
10
|
+
name: "search_notes",
|
|
11
|
+
label: "Search Notes",
|
|
12
|
+
description: "ๆ็ดข็จๆท่ฎพๅคไธ็ๅคๅฟๅฝๅ
ๅฎนใๆ นๆฎๅ
ณ้ฎ่ฏๅจๅคๅฟๅฝ็ๆ ้ขใๅ
ๅฎนๅ้ไปถๅ็งฐไธญ่ฟ่กๆฃ็ดขใ",
|
|
13
|
+
parameters: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
query: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "ๆ็ดขๅ
ณ้ฎ่ฏ๏ผ็จไบๅจๅคๅฟๅฝไธญๆฃ็ดข็ธๅ
ณๅ
ๅฎน",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
required: ["query"],
|
|
22
|
+
},
|
|
23
|
+
async execute(toolCallId, params) {
|
|
24
|
+
logger.debug("Executing search note tool, toolCallId:", toolCallId);
|
|
25
|
+
// Validate parameters
|
|
26
|
+
if (!params.query) {
|
|
27
|
+
throw new Error("Missing required parameter: query is required");
|
|
28
|
+
}
|
|
29
|
+
// Get session context
|
|
30
|
+
const sessionContext = getLatestSessionContext();
|
|
31
|
+
if (!sessionContext) {
|
|
32
|
+
throw new Error("No active XY session found. Search note tool can only be used during an active conversation.");
|
|
33
|
+
}
|
|
34
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
35
|
+
// Get WebSocket manager
|
|
36
|
+
const wsManager = getXYWebSocketManager(config);
|
|
37
|
+
// Build SearchNote command
|
|
38
|
+
const command = {
|
|
39
|
+
header: {
|
|
40
|
+
namespace: "Common",
|
|
41
|
+
name: "Action",
|
|
42
|
+
},
|
|
43
|
+
payload: {
|
|
44
|
+
cardParam: {},
|
|
45
|
+
executeParam: {
|
|
46
|
+
executeMode: "background",
|
|
47
|
+
intentName: "SearchNote",
|
|
48
|
+
bundleName: "com.huawei.hmos.notepad",
|
|
49
|
+
dimension: "",
|
|
50
|
+
needUnlock: true,
|
|
51
|
+
actionResponse: true,
|
|
52
|
+
timeOut: 5,
|
|
53
|
+
intentParam: {
|
|
54
|
+
query: params.query,
|
|
55
|
+
},
|
|
56
|
+
achieveType: "INTENT",
|
|
57
|
+
},
|
|
58
|
+
responses: [
|
|
59
|
+
{
|
|
60
|
+
resultCode: "",
|
|
61
|
+
displayText: "",
|
|
62
|
+
ttsText: "",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
needUploadResult: true,
|
|
66
|
+
noHalfPage: false,
|
|
67
|
+
pageControlRelated: false,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
// Send command and wait for response (5 second timeout)
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const timeout = setTimeout(() => {
|
|
73
|
+
wsManager.off("data-event", handler);
|
|
74
|
+
reject(new Error("ๆ็ดขๅคๅฟๅฝ่ถ
ๆถ๏ผ5็ง๏ผ"));
|
|
75
|
+
}, 5000);
|
|
76
|
+
// Listen for data events from WebSocket
|
|
77
|
+
const handler = (event) => {
|
|
78
|
+
logger.debug("Received data event:", event);
|
|
79
|
+
if (event.intentName === "SearchNote") {
|
|
80
|
+
clearTimeout(timeout);
|
|
81
|
+
wsManager.off("data-event", handler);
|
|
82
|
+
if (event.status === "success" && event.outputs) {
|
|
83
|
+
const { result, code } = event.outputs;
|
|
84
|
+
const items = result?.items || [];
|
|
85
|
+
logger.log(`Notes found: ${items.length} results for query "${params.query}"`);
|
|
86
|
+
resolve({
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: JSON.stringify({
|
|
91
|
+
success: true,
|
|
92
|
+
query: params.query,
|
|
93
|
+
totalResults: items.length,
|
|
94
|
+
notes: items.map((item) => ({
|
|
95
|
+
entityId: item.entityId,
|
|
96
|
+
entityName: item.entityName,
|
|
97
|
+
title: item.title?.replace(/<\/?em>/g, ''), // Remove <em> tags
|
|
98
|
+
content: item.content,
|
|
99
|
+
createdDate: item.createdDate,
|
|
100
|
+
modifiedDate: item.modifiedDate,
|
|
101
|
+
})),
|
|
102
|
+
indexName: result?.indexName,
|
|
103
|
+
code,
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
reject(new Error(`ๆ็ดขๅคๅฟๅฝๅคฑ่ดฅ: ${event.status}`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
// Register event handler
|
|
115
|
+
wsManager.on("data-event", handler);
|
|
116
|
+
// Send the command
|
|
117
|
+
sendCommand({
|
|
118
|
+
config,
|
|
119
|
+
sessionId,
|
|
120
|
+
taskId,
|
|
121
|
+
messageId,
|
|
122
|
+
command,
|
|
123
|
+
}).catch((error) => {
|
|
124
|
+
clearTimeout(timeout);
|
|
125
|
+
wsManager.off("data-event", handler);
|
|
126
|
+
reject(error);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.js";
|
|
1
2
|
// Map of sessionKey -> SessionContext
|
|
2
3
|
const activeSessions = new Map();
|
|
3
4
|
/**
|
|
@@ -5,21 +6,42 @@ const activeSessions = new Map();
|
|
|
5
6
|
* Should be called when starting to process a message.
|
|
6
7
|
*/
|
|
7
8
|
export function registerSession(sessionKey, context) {
|
|
9
|
+
logger.log(`[SESSION_MANAGER] ๐ Registering session: ${sessionKey}`);
|
|
10
|
+
logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
|
|
11
|
+
logger.log(`[SESSION_MANAGER] - taskId: ${context.taskId}`);
|
|
12
|
+
logger.log(`[SESSION_MANAGER] - messageId: ${context.messageId}`);
|
|
13
|
+
logger.log(`[SESSION_MANAGER] - agentId: ${context.agentId}`);
|
|
14
|
+
logger.log(`[SESSION_MANAGER] - Active sessions before: ${activeSessions.size}`);
|
|
8
15
|
activeSessions.set(sessionKey, context);
|
|
16
|
+
logger.log(`[SESSION_MANAGER] - Active sessions after: ${activeSessions.size}`);
|
|
17
|
+
logger.log(`[SESSION_MANAGER] - All session keys: [${Array.from(activeSessions.keys()).join(", ")}]`);
|
|
9
18
|
}
|
|
10
19
|
/**
|
|
11
20
|
* Unregister a session context.
|
|
12
21
|
* Should be called when message processing is complete.
|
|
13
22
|
*/
|
|
14
23
|
export function unregisterSession(sessionKey) {
|
|
15
|
-
|
|
24
|
+
logger.log(`[SESSION_MANAGER] ๐๏ธ Unregistering session: ${sessionKey}`);
|
|
25
|
+
logger.log(`[SESSION_MANAGER] - Active sessions before: ${activeSessions.size}`);
|
|
26
|
+
logger.log(`[SESSION_MANAGER] - Session existed: ${activeSessions.has(sessionKey)}`);
|
|
27
|
+
const existed = activeSessions.delete(sessionKey);
|
|
28
|
+
logger.log(`[SESSION_MANAGER] - Deleted: ${existed}`);
|
|
29
|
+
logger.log(`[SESSION_MANAGER] - Active sessions after: ${activeSessions.size}`);
|
|
30
|
+
logger.log(`[SESSION_MANAGER] - Remaining session keys: [${Array.from(activeSessions.keys()).join(", ")}]`);
|
|
16
31
|
}
|
|
17
32
|
/**
|
|
18
33
|
* Get session context by sessionKey.
|
|
19
34
|
* Returns null if session not found.
|
|
20
35
|
*/
|
|
21
36
|
export function getSessionContext(sessionKey) {
|
|
22
|
-
|
|
37
|
+
logger.log(`[SESSION_MANAGER] ๐ Getting session by key: ${sessionKey}`);
|
|
38
|
+
logger.log(`[SESSION_MANAGER] - Active sessions: ${activeSessions.size}`);
|
|
39
|
+
const context = activeSessions.get(sessionKey) ?? null;
|
|
40
|
+
logger.log(`[SESSION_MANAGER] - Found: ${context !== null}`);
|
|
41
|
+
if (context) {
|
|
42
|
+
logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
|
|
43
|
+
}
|
|
44
|
+
return context;
|
|
23
45
|
}
|
|
24
46
|
/**
|
|
25
47
|
* Get the most recent session context.
|
|
@@ -27,9 +49,19 @@ export function getSessionContext(sessionKey) {
|
|
|
27
49
|
* Returns null if no sessions are active.
|
|
28
50
|
*/
|
|
29
51
|
export function getLatestSessionContext() {
|
|
30
|
-
|
|
52
|
+
logger.log(`[SESSION_MANAGER] ๐ Getting latest session context`);
|
|
53
|
+
logger.log(`[SESSION_MANAGER] - Active sessions count: ${activeSessions.size}`);
|
|
54
|
+
logger.log(`[SESSION_MANAGER] - Active session keys: [${Array.from(activeSessions.keys()).join(", ")}]`);
|
|
55
|
+
if (activeSessions.size === 0) {
|
|
56
|
+
logger.error(`[SESSION_MANAGER] - โ No active sessions found!`);
|
|
31
57
|
return null;
|
|
58
|
+
}
|
|
32
59
|
// Return the last added session
|
|
33
60
|
const sessions = Array.from(activeSessions.values());
|
|
34
|
-
|
|
61
|
+
const latestSession = sessions[sessions.length - 1];
|
|
62
|
+
logger.log(`[SESSION_MANAGER] - โ
Found latest session:`);
|
|
63
|
+
logger.log(`[SESSION_MANAGER] - sessionId: ${latestSession.sessionId}`);
|
|
64
|
+
logger.log(`[SESSION_MANAGER] - taskId: ${latestSession.taskId}`);
|
|
65
|
+
logger.log(`[SESSION_MANAGER] - messageId: ${latestSession.messageId}`);
|
|
66
|
+
return latestSession;
|
|
35
67
|
}
|
package/dist/src/websocket.js
CHANGED
|
@@ -274,6 +274,7 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
274
274
|
* Handle incoming message from server.
|
|
275
275
|
*/
|
|
276
276
|
handleMessage(serverId, data) {
|
|
277
|
+
console.log(`[WEBSOCKET-HANDLE] >>>>>>> serverId: ${serverId}, receiving message... <<<<<<<`);
|
|
277
278
|
try {
|
|
278
279
|
const messageStr = data.toString();
|
|
279
280
|
const parsed = JSON.parse(messageStr);
|
|
@@ -324,6 +325,7 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
324
325
|
return; // Don't emit message event
|
|
325
326
|
}
|
|
326
327
|
// Emit message event for non-data-only messages
|
|
328
|
+
console.log(`[XY-${serverId}] *** EMITTING message event (Direct A2A path) ***`);
|
|
327
329
|
this.emit("message", a2aRequest, sessionId, serverId);
|
|
328
330
|
return;
|
|
329
331
|
}
|
|
@@ -377,6 +379,7 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
377
379
|
}
|
|
378
380
|
console.log(`[XY-${serverId}] Session ID: ${sessionId}`);
|
|
379
381
|
// Emit message event
|
|
382
|
+
console.log(`[XY-${serverId}] *** EMITTING message event (Wrapped path) ***`);
|
|
380
383
|
this.emit("message", a2aRequest, sessionId, serverId);
|
|
381
384
|
}
|
|
382
385
|
catch (error) {
|