@ynhcj/xiaoyi-channel 0.0.26-beta → 0.0.27-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 +13 -3
- package/dist/src/channel.js +10 -1
- package/dist/src/tools/calendar-tool.js +2 -2
- package/dist/src/tools/call-phone-tool.d.ts +5 -0
- package/dist/src/tools/call-phone-tool.js +183 -0
- package/dist/src/tools/create-alarm-tool.d.ts +7 -0
- package/dist/src/tools/create-alarm-tool.js +395 -0
- package/dist/src/tools/delete-alarm-tool.d.ts +11 -0
- package/dist/src/tools/delete-alarm-tool.js +238 -0
- package/dist/src/tools/location-tool.js +2 -2
- package/dist/src/tools/modify-alarm-tool.d.ts +9 -0
- package/dist/src/tools/modify-alarm-tool.js +433 -0
- package/dist/src/tools/modify-note-tool.js +2 -2
- package/dist/src/tools/note-tool.js +2 -2
- package/dist/src/tools/search-alarm-tool.d.ts +8 -0
- package/dist/src/tools/search-alarm-tool.js +389 -0
- package/dist/src/tools/search-calendar-tool.js +2 -2
- package/dist/src/tools/search-contact-tool.js +2 -2
- package/dist/src/tools/search-file-tool.d.ts +5 -0
- package/dist/src/tools/search-file-tool.js +173 -0
- package/dist/src/tools/search-message-tool.d.ts +5 -0
- package/dist/src/tools/search-message-tool.js +173 -0
- package/dist/src/tools/search-note-tool.js +2 -2
- package/dist/src/tools/search-photo-gallery-tool.js +2 -2
- package/dist/src/tools/send-message-tool.d.ts +5 -0
- package/dist/src/tools/send-message-tool.js +189 -0
- package/dist/src/tools/session-manager.d.ts +12 -0
- package/dist/src/tools/session-manager.js +33 -0
- package/dist/src/tools/upload-file-tool.d.ts +13 -0
- package/dist/src/tools/upload-file-tool.js +265 -0
- package/dist/src/tools/upload-photo-tool.js +2 -2
- package/dist/src/tools/xiaoyi-gui-tool.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* XY search message tool - searches SMS messages on user's device.
|
|
7
|
+
* Returns matching messages based on content keyword search.
|
|
8
|
+
*/
|
|
9
|
+
export const searchMessageTool = {
|
|
10
|
+
name: "search_message",
|
|
11
|
+
label: "Search Message",
|
|
12
|
+
description: "搜索手机短信。根据关键词搜索短信内容。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
|
|
13
|
+
parameters: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
content: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "搜索关键词,用于在短信内容中进行匹配",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
required: ["content"],
|
|
22
|
+
},
|
|
23
|
+
async execute(toolCallId, params) {
|
|
24
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🚀 Starting execution`);
|
|
25
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - toolCallId: ${toolCallId}`);
|
|
26
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - params:`, JSON.stringify(params));
|
|
27
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
28
|
+
// Validate content parameter
|
|
29
|
+
if (!params.content || typeof params.content !== "string" || params.content.trim() === "") {
|
|
30
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ❌ Missing or invalid content parameter`);
|
|
31
|
+
throw new Error("Missing required parameter: content must be a non-empty string");
|
|
32
|
+
}
|
|
33
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🔍 Searching for messages with keyword: ${params.content}`);
|
|
34
|
+
// Get session context
|
|
35
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🔍 Attempting to get session context...`);
|
|
36
|
+
const sessionContext = getCurrentSessionContext();
|
|
37
|
+
if (!sessionContext) {
|
|
38
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ❌ FAILED: No active session found!`);
|
|
39
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] - toolCallId: ${toolCallId}`);
|
|
40
|
+
throw new Error("No active XY session found. Search message tool can only be used during an active conversation.");
|
|
41
|
+
}
|
|
42
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] ✅ Session context found`);
|
|
43
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
44
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
45
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
46
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
47
|
+
// Get WebSocket manager
|
|
48
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🔌 Getting WebSocket manager...`);
|
|
49
|
+
const wsManager = getXYWebSocketManager(config);
|
|
50
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] ✅ WebSocket manager obtained`);
|
|
51
|
+
// Build SearchMessage command
|
|
52
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📦 Building SearchMessage command...`);
|
|
53
|
+
const command = {
|
|
54
|
+
header: {
|
|
55
|
+
namespace: "Common",
|
|
56
|
+
name: "Action",
|
|
57
|
+
},
|
|
58
|
+
payload: {
|
|
59
|
+
cardParam: {},
|
|
60
|
+
executeParam: {
|
|
61
|
+
executeMode: "background",
|
|
62
|
+
intentName: "SearchMessage",
|
|
63
|
+
bundleName: "com.huawei.hmos.aidispatchservice",
|
|
64
|
+
needUnlock: true,
|
|
65
|
+
actionResponse: true,
|
|
66
|
+
appType: "OHOS_APP",
|
|
67
|
+
timeOut: 5,
|
|
68
|
+
intentParam: {
|
|
69
|
+
content: params.content.trim(),
|
|
70
|
+
},
|
|
71
|
+
permissionId: [],
|
|
72
|
+
achieveType: "INTENT",
|
|
73
|
+
},
|
|
74
|
+
responses: [
|
|
75
|
+
{
|
|
76
|
+
resultCode: "",
|
|
77
|
+
displayText: "",
|
|
78
|
+
ttsText: "",
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
needUploadResult: true,
|
|
82
|
+
noHalfPage: false,
|
|
83
|
+
pageControlRelated: false,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📋 Command details:`, JSON.stringify(command, null, 2));
|
|
87
|
+
// Send command and wait for response (60 second timeout)
|
|
88
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] ⏳ Setting up promise to wait for message search response...`);
|
|
89
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - Timeout: 60 seconds`);
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const timeout = setTimeout(() => {
|
|
92
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ⏰ Timeout: No response received within 60 seconds`);
|
|
93
|
+
wsManager.off("data-event", handler);
|
|
94
|
+
reject(new Error("搜索短信超时(60秒)"));
|
|
95
|
+
}, 60000);
|
|
96
|
+
// Listen for data events from WebSocket
|
|
97
|
+
const handler = (event) => {
|
|
98
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
99
|
+
if (event.intentName === "SearchMessage") {
|
|
100
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🎯 SearchMessage event received`);
|
|
101
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - status: ${event.status}`);
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
wsManager.off("data-event", handler);
|
|
104
|
+
if (event.status === "success" && event.outputs) {
|
|
105
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] ✅ Message search response received`);
|
|
106
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - outputs:`, JSON.stringify(event.outputs));
|
|
107
|
+
// Check for error code in outputs
|
|
108
|
+
const code = event.outputs.code !== undefined ? event.outputs.code : null;
|
|
109
|
+
if (code !== null && code !== 0) {
|
|
110
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ❌ Device returned error`);
|
|
111
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] - code: ${code}`);
|
|
112
|
+
const errorMsg = event.outputs.errorMsg || event.outputs.errMsg || "未知错误";
|
|
113
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] - errorMsg: ${errorMsg}`);
|
|
114
|
+
reject(new Error(`搜索短信失败: ${errorMsg} (错误代码: ${code})`));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Extract result.items with safe checks
|
|
118
|
+
const result = event.outputs.result;
|
|
119
|
+
let items = [];
|
|
120
|
+
if (result && typeof result === "object" && Array.isArray(result.items)) {
|
|
121
|
+
items = result.items;
|
|
122
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📋 Found ${items.length} message(s)`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
logger.warn(`[SEARCH_MESSAGE_TOOL] ⚠️ No items found in result or result is invalid`);
|
|
126
|
+
logger.warn(`[SEARCH_MESSAGE_TOOL] - result:`, JSON.stringify(result || {}));
|
|
127
|
+
}
|
|
128
|
+
// Return items array as JSON string
|
|
129
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 🎉 Message search completed successfully`);
|
|
130
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - keyword: ${params.content}`);
|
|
131
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] - result count: ${items.length}`);
|
|
132
|
+
resolve({
|
|
133
|
+
content: [
|
|
134
|
+
{
|
|
135
|
+
type: "text",
|
|
136
|
+
text: JSON.stringify(items),
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ❌ Message search failed`);
|
|
143
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] - status: ${event.status}`);
|
|
144
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] - outputs:`, JSON.stringify(event.outputs || {}));
|
|
145
|
+
const errorDetail = event.outputs ? JSON.stringify(event.outputs) : event.status;
|
|
146
|
+
reject(new Error(`搜索短信失败: ${errorDetail}`));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
// Register event handler
|
|
151
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📡 Registering data-event handler on WebSocket manager`);
|
|
152
|
+
wsManager.on("data-event", handler);
|
|
153
|
+
// Send the command
|
|
154
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] 📤 Sending SearchMessage command...`);
|
|
155
|
+
sendCommand({
|
|
156
|
+
config,
|
|
157
|
+
sessionId,
|
|
158
|
+
taskId,
|
|
159
|
+
messageId,
|
|
160
|
+
command,
|
|
161
|
+
})
|
|
162
|
+
.then(() => {
|
|
163
|
+
logger.log(`[SEARCH_MESSAGE_TOOL] ✅ Command sent successfully, waiting for response...`);
|
|
164
|
+
})
|
|
165
|
+
.catch((error) => {
|
|
166
|
+
logger.error(`[SEARCH_MESSAGE_TOOL] ❌ Failed to send command:`, error);
|
|
167
|
+
clearTimeout(timeout);
|
|
168
|
+
wsManager.off("data-event", handler);
|
|
169
|
+
reject(error);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getXYWebSocketManager } from "../client.js";
|
|
2
2
|
import { sendCommand } from "../formatter.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
4
|
import { logger } from "../utils/logger.js";
|
|
5
5
|
/**
|
|
6
6
|
* XY search note tool - searches notes on user's device.
|
|
@@ -27,7 +27,7 @@ export const searchNoteTool = {
|
|
|
27
27
|
throw new Error("Missing required parameter: query is required");
|
|
28
28
|
}
|
|
29
29
|
// Get session context
|
|
30
|
-
const sessionContext =
|
|
30
|
+
const sessionContext = getCurrentSessionContext();
|
|
31
31
|
if (!sessionContext) {
|
|
32
32
|
throw new Error("No active XY session found. Search note tool can only be used during an active conversation.");
|
|
33
33
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getXYWebSocketManager } from "../client.js";
|
|
2
2
|
import { sendCommand } from "../formatter.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
4
|
import { logger } from "../utils/logger.js";
|
|
5
5
|
/**
|
|
6
6
|
* XY search photo gallery tool - searches photos in user's gallery.
|
|
@@ -46,7 +46,7 @@ export const searchPhotoGalleryTool = {
|
|
|
46
46
|
}
|
|
47
47
|
// Get session context
|
|
48
48
|
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 🔍 Attempting to get session context...`);
|
|
49
|
-
const sessionContext =
|
|
49
|
+
const sessionContext = getCurrentSessionContext();
|
|
50
50
|
if (!sessionContext) {
|
|
51
51
|
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] ❌ FAILED: No active session found!`);
|
|
52
52
|
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] - toolCallId: ${toolCallId}`);
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* XY send message tool - sends SMS message on user's device.
|
|
7
|
+
* Requires phoneNumber (with +86 prefix) and content parameters.
|
|
8
|
+
*/
|
|
9
|
+
export const sendMessageTool = {
|
|
10
|
+
name: "send_message",
|
|
11
|
+
label: "Send Message",
|
|
12
|
+
description: "通过手机发送短信。需要提供接收方手机号码和短信内容。手机号码会自动添加+86前缀(如果没有的话)。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
|
|
13
|
+
parameters: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
phoneNumber: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "接收方手机号码(会自动添加+86前缀)",
|
|
19
|
+
},
|
|
20
|
+
content: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "短信内容",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
required: ["phoneNumber", "content"],
|
|
26
|
+
},
|
|
27
|
+
async execute(toolCallId, params) {
|
|
28
|
+
logger.log(`[SEND_MESSAGE_TOOL] 🚀 Starting execution`);
|
|
29
|
+
logger.log(`[SEND_MESSAGE_TOOL] - toolCallId: ${toolCallId}`);
|
|
30
|
+
logger.log(`[SEND_MESSAGE_TOOL] - params:`, JSON.stringify(params));
|
|
31
|
+
logger.log(`[SEND_MESSAGE_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
32
|
+
// Validate phoneNumber parameter
|
|
33
|
+
if (!params.phoneNumber || typeof params.phoneNumber !== "string" || params.phoneNumber.trim() === "") {
|
|
34
|
+
logger.error(`[SEND_MESSAGE_TOOL] ❌ Missing or invalid phoneNumber parameter`);
|
|
35
|
+
throw new Error("Missing required parameter: phoneNumber must be a non-empty string");
|
|
36
|
+
}
|
|
37
|
+
// Validate content parameter
|
|
38
|
+
if (!params.content || typeof params.content !== "string" || params.content.trim() === "") {
|
|
39
|
+
logger.error(`[SEND_MESSAGE_TOOL] ❌ Missing or invalid content parameter`);
|
|
40
|
+
throw new Error("Missing required parameter: content must be a non-empty string");
|
|
41
|
+
}
|
|
42
|
+
// Normalize phone number: add +86 prefix if not present
|
|
43
|
+
let phoneNumber = params.phoneNumber.trim();
|
|
44
|
+
if (!phoneNumber.startsWith("+86")) {
|
|
45
|
+
// Remove leading 0 if present (e.g., 086 -> 86)
|
|
46
|
+
if (phoneNumber.startsWith("0")) {
|
|
47
|
+
phoneNumber = phoneNumber.substring(1);
|
|
48
|
+
}
|
|
49
|
+
// Remove +86 or 86 prefix if already present to avoid duplication
|
|
50
|
+
if (phoneNumber.startsWith("86")) {
|
|
51
|
+
phoneNumber = phoneNumber.substring(2);
|
|
52
|
+
}
|
|
53
|
+
phoneNumber = `+86${phoneNumber}`;
|
|
54
|
+
logger.log(`[SEND_MESSAGE_TOOL] 📞 Normalized phone number: ${params.phoneNumber} -> ${phoneNumber}`);
|
|
55
|
+
}
|
|
56
|
+
logger.log(`[SEND_MESSAGE_TOOL] 📤 Preparing to send message`);
|
|
57
|
+
logger.log(`[SEND_MESSAGE_TOOL] - phoneNumber: ${phoneNumber}`);
|
|
58
|
+
logger.log(`[SEND_MESSAGE_TOOL] - content length: ${params.content.length} characters`);
|
|
59
|
+
// Get session context
|
|
60
|
+
logger.log(`[SEND_MESSAGE_TOOL] 🔍 Attempting to get session context...`);
|
|
61
|
+
const sessionContext = getCurrentSessionContext();
|
|
62
|
+
if (!sessionContext) {
|
|
63
|
+
logger.error(`[SEND_MESSAGE_TOOL] ❌ FAILED: No active session found!`);
|
|
64
|
+
logger.error(`[SEND_MESSAGE_TOOL] - toolCallId: ${toolCallId}`);
|
|
65
|
+
throw new Error("No active XY session found. Send message tool can only be used during an active conversation.");
|
|
66
|
+
}
|
|
67
|
+
logger.log(`[SEND_MESSAGE_TOOL] ✅ Session context found`);
|
|
68
|
+
logger.log(`[SEND_MESSAGE_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
69
|
+
logger.log(`[SEND_MESSAGE_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
70
|
+
logger.log(`[SEND_MESSAGE_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
71
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
72
|
+
// Get WebSocket manager
|
|
73
|
+
logger.log(`[SEND_MESSAGE_TOOL] 🔌 Getting WebSocket manager...`);
|
|
74
|
+
const wsManager = getXYWebSocketManager(config);
|
|
75
|
+
logger.log(`[SEND_MESSAGE_TOOL] ✅ WebSocket manager obtained`);
|
|
76
|
+
// Build SendShortMessage command
|
|
77
|
+
logger.log(`[SEND_MESSAGE_TOOL] 📦 Building SendShortMessage command...`);
|
|
78
|
+
const command = {
|
|
79
|
+
header: {
|
|
80
|
+
namespace: "Common",
|
|
81
|
+
name: "Action",
|
|
82
|
+
},
|
|
83
|
+
payload: {
|
|
84
|
+
cardParam: {},
|
|
85
|
+
executeParam: {
|
|
86
|
+
executeMode: "background",
|
|
87
|
+
intentName: "SendShortMessage",
|
|
88
|
+
bundleName: "com.huawei.hmos.aidispatchservice",
|
|
89
|
+
needUnlock: true,
|
|
90
|
+
actionResponse: true,
|
|
91
|
+
appType: "OHOS_APP",
|
|
92
|
+
timeOut: 5,
|
|
93
|
+
intentParam: {
|
|
94
|
+
phoneNumber: phoneNumber,
|
|
95
|
+
content: params.content.trim(),
|
|
96
|
+
},
|
|
97
|
+
permissionId: [],
|
|
98
|
+
achieveType: "INTENT",
|
|
99
|
+
},
|
|
100
|
+
responses: [
|
|
101
|
+
{
|
|
102
|
+
resultCode: "",
|
|
103
|
+
displayText: "",
|
|
104
|
+
ttsText: "",
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
needUploadResult: true,
|
|
108
|
+
noHalfPage: false,
|
|
109
|
+
pageControlRelated: false,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
logger.log(`[SEND_MESSAGE_TOOL] 📋 Command details:`, JSON.stringify(command, null, 2));
|
|
113
|
+
// Send command and wait for response (60 second timeout)
|
|
114
|
+
logger.log(`[SEND_MESSAGE_TOOL] ⏳ Setting up promise to wait for send message response...`);
|
|
115
|
+
logger.log(`[SEND_MESSAGE_TOOL] - Timeout: 60 seconds`);
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const timeout = setTimeout(() => {
|
|
118
|
+
logger.error(`[SEND_MESSAGE_TOOL] ⏰ Timeout: No response received within 60 seconds`);
|
|
119
|
+
wsManager.off("data-event", handler);
|
|
120
|
+
reject(new Error("发送短信超时(60秒)"));
|
|
121
|
+
}, 60000);
|
|
122
|
+
// Listen for data events from WebSocket
|
|
123
|
+
const handler = (event) => {
|
|
124
|
+
logger.log(`[SEND_MESSAGE_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
125
|
+
if (event.intentName === "SendShortMessage") {
|
|
126
|
+
logger.log(`[SEND_MESSAGE_TOOL] 🎯 SendShortMessage event received`);
|
|
127
|
+
logger.log(`[SEND_MESSAGE_TOOL] - status: ${event.status}`);
|
|
128
|
+
clearTimeout(timeout);
|
|
129
|
+
wsManager.off("data-event", handler);
|
|
130
|
+
if (event.status === "success" && event.outputs) {
|
|
131
|
+
logger.log(`[SEND_MESSAGE_TOOL] ✅ Send message response received`);
|
|
132
|
+
logger.log(`[SEND_MESSAGE_TOOL] - outputs:`, JSON.stringify(event.outputs));
|
|
133
|
+
// Check for error code in outputs
|
|
134
|
+
const code = event.outputs.code !== undefined ? event.outputs.code : null;
|
|
135
|
+
if (code !== null && code !== 0) {
|
|
136
|
+
logger.error(`[SEND_MESSAGE_TOOL] ❌ Device returned error`);
|
|
137
|
+
logger.error(`[SEND_MESSAGE_TOOL] - code: ${code}`);
|
|
138
|
+
const errorMsg = event.outputs.errorMsg || event.outputs.errMsg || "未知错误";
|
|
139
|
+
logger.error(`[SEND_MESSAGE_TOOL] - errorMsg: ${errorMsg}`);
|
|
140
|
+
reject(new Error(`发送短信失败: ${errorMsg} (错误代码: ${code})`));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Extract result with safe checks
|
|
144
|
+
const result = event.outputs.result || {};
|
|
145
|
+
logger.log(`[SEND_MESSAGE_TOOL] 🎉 Message sent successfully`);
|
|
146
|
+
logger.log(`[SEND_MESSAGE_TOOL] - phoneNumber: ${phoneNumber}`);
|
|
147
|
+
logger.log(`[SEND_MESSAGE_TOOL] - result:`, JSON.stringify(result));
|
|
148
|
+
resolve({
|
|
149
|
+
content: [
|
|
150
|
+
{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: JSON.stringify(result),
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
logger.error(`[SEND_MESSAGE_TOOL] ❌ Send message failed`);
|
|
159
|
+
logger.error(`[SEND_MESSAGE_TOOL] - status: ${event.status}`);
|
|
160
|
+
logger.error(`[SEND_MESSAGE_TOOL] - outputs:`, JSON.stringify(event.outputs || {}));
|
|
161
|
+
const errorDetail = event.outputs ? JSON.stringify(event.outputs) : event.status;
|
|
162
|
+
reject(new Error(`发送短信失败: ${errorDetail}`));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
// Register event handler
|
|
167
|
+
logger.log(`[SEND_MESSAGE_TOOL] 📡 Registering data-event handler on WebSocket manager`);
|
|
168
|
+
wsManager.on("data-event", handler);
|
|
169
|
+
// Send the command
|
|
170
|
+
logger.log(`[SEND_MESSAGE_TOOL] 📤 Sending SendShortMessage command...`);
|
|
171
|
+
sendCommand({
|
|
172
|
+
config,
|
|
173
|
+
sessionId,
|
|
174
|
+
taskId,
|
|
175
|
+
messageId,
|
|
176
|
+
command,
|
|
177
|
+
})
|
|
178
|
+
.then(() => {
|
|
179
|
+
logger.log(`[SEND_MESSAGE_TOOL] ✅ Command sent successfully, waiting for response...`);
|
|
180
|
+
})
|
|
181
|
+
.catch((error) => {
|
|
182
|
+
logger.error(`[SEND_MESSAGE_TOOL] ❌ Failed to send command:`, error);
|
|
183
|
+
clearTimeout(timeout);
|
|
184
|
+
wsManager.off("data-event", handler);
|
|
185
|
+
reject(error);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
};
|
|
@@ -23,7 +23,19 @@ export declare function unregisterSession(sessionKey: string): void;
|
|
|
23
23
|
export declare function getSessionContext(sessionKey: string): SessionContext | null;
|
|
24
24
|
/**
|
|
25
25
|
* Get the most recent session context.
|
|
26
|
+
* @deprecated Use getCurrentSessionContext() instead for thread-safe access.
|
|
26
27
|
* This is a fallback for tools that don't have access to sessionKey.
|
|
27
28
|
* Returns null if no sessions are active.
|
|
28
29
|
*/
|
|
29
30
|
export declare function getLatestSessionContext(): SessionContext | null;
|
|
31
|
+
/**
|
|
32
|
+
* Run a callback with a session context stored in AsyncLocalStorage.
|
|
33
|
+
* This ensures thread-safe context isolation for concurrent requests.
|
|
34
|
+
*/
|
|
35
|
+
export declare function runWithSessionContext<T>(context: SessionContext, callback: () => Promise<T>): Promise<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Get the current session context from AsyncLocalStorage.
|
|
38
|
+
* This is the recommended way to access session context in tools.
|
|
39
|
+
* Returns null if not running within a session context.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getCurrentSessionContext(): SessionContext | null;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
// Session manager for XY tool context
|
|
2
|
+
// Stores active session contexts that tools can access
|
|
3
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
1
4
|
import { logger } from "../utils/logger.js";
|
|
2
5
|
import { configManager } from "../utils/config-manager.js";
|
|
3
6
|
// Map of sessionKey -> SessionContextWithRef
|
|
4
7
|
const activeSessions = new Map();
|
|
8
|
+
// AsyncLocalStorage for thread-safe session context isolation
|
|
9
|
+
const asyncLocalStorage = new AsyncLocalStorage();
|
|
5
10
|
/**
|
|
6
11
|
* Register a session context for tool access.
|
|
7
12
|
* Should be called when starting to process a message.
|
|
@@ -74,6 +79,7 @@ export function getSessionContext(sessionKey) {
|
|
|
74
79
|
}
|
|
75
80
|
/**
|
|
76
81
|
* Get the most recent session context.
|
|
82
|
+
* @deprecated Use getCurrentSessionContext() instead for thread-safe access.
|
|
77
83
|
* This is a fallback for tools that don't have access to sessionKey.
|
|
78
84
|
* Returns null if no sessions are active.
|
|
79
85
|
*/
|
|
@@ -96,3 +102,30 @@ export function getLatestSessionContext() {
|
|
|
96
102
|
const { refCount, ...latestSession } = latestSessionWithRef;
|
|
97
103
|
return latestSession;
|
|
98
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Run a callback with a session context stored in AsyncLocalStorage.
|
|
107
|
+
* This ensures thread-safe context isolation for concurrent requests.
|
|
108
|
+
*/
|
|
109
|
+
export function runWithSessionContext(context, callback) {
|
|
110
|
+
logger.log(`[SESSION_MANAGER] 🔐 Running with AsyncLocalStorage context`);
|
|
111
|
+
logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
|
|
112
|
+
logger.log(`[SESSION_MANAGER] - taskId: ${context.taskId}`);
|
|
113
|
+
return asyncLocalStorage.run(context, callback);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get the current session context from AsyncLocalStorage.
|
|
117
|
+
* This is the recommended way to access session context in tools.
|
|
118
|
+
* Returns null if not running within a session context.
|
|
119
|
+
*/
|
|
120
|
+
export function getCurrentSessionContext() {
|
|
121
|
+
const context = asyncLocalStorage.getStore() ?? null;
|
|
122
|
+
if (context) {
|
|
123
|
+
logger.log(`[SESSION_MANAGER] ✅ Got current session context from AsyncLocalStorage`);
|
|
124
|
+
logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
|
|
125
|
+
logger.log(`[SESSION_MANAGER] - taskId: ${context.taskId}`);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
logger.warn(`[SESSION_MANAGER] ⚠️ No session context in AsyncLocalStorage`);
|
|
129
|
+
}
|
|
130
|
+
return context;
|
|
131
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XY upload file tool - uploads local files to get publicly accessible URLs.
|
|
3
|
+
* Requires file URIs from search_file tool as prerequisite.
|
|
4
|
+
*
|
|
5
|
+
* Prerequisites:
|
|
6
|
+
* 1. Call search_file tool first to get URIs of files
|
|
7
|
+
* 2. Use the URIs to get public URLs
|
|
8
|
+
*
|
|
9
|
+
* Usage Note:
|
|
10
|
+
* - After getting public URLs, if further processing is needed, download the file first
|
|
11
|
+
* - URLs returned are publicly accessible
|
|
12
|
+
*/
|
|
13
|
+
export declare const uploadFileTool: any;
|