@ynhcj/xiaoyi-channel 0.0.44-beta → 0.0.44-next
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/index.d.ts +0 -2
- package/dist/index.js +42 -2
- package/dist/src/bot.d.ts +1 -0
- package/dist/src/bot.js +59 -61
- package/dist/src/channel.js +30 -4
- package/dist/src/client.js +0 -9
- package/dist/src/cspl/call-api.d.ts +3 -0
- package/dist/src/cspl/call-api.js +86 -0
- package/dist/src/cspl/config.d.ts +19 -0
- package/dist/src/cspl/config.js +50 -0
- package/dist/src/cspl/constants.d.ts +43 -0
- package/dist/src/cspl/constants.js +22 -0
- package/dist/src/cspl/utils.d.ts +10 -0
- package/dist/src/cspl/utils.js +57 -0
- package/dist/src/file-upload.d.ts +5 -0
- package/dist/src/file-upload.js +88 -6
- package/dist/src/formatter.d.ts +2 -0
- package/dist/src/formatter.js +13 -30
- package/dist/src/heartbeat.js +0 -4
- package/dist/src/monitor.js +8 -10
- package/dist/src/onboarding.d.ts +3 -4
- package/dist/src/onboarding.js +2 -2
- package/dist/src/outbound.d.ts +2 -1
- package/dist/src/outbound.js +1 -19
- package/dist/src/parser.d.ts +6 -0
- package/dist/src/parser.js +16 -0
- package/dist/src/push.js +0 -21
- package/dist/src/reply-dispatcher.d.ts +4 -0
- package/dist/src/reply-dispatcher.js +46 -8
- package/dist/src/steer-injector.d.ts +16 -0
- package/dist/src/steer-injector.js +74 -0
- package/dist/src/thread-bindings.d.ts +54 -0
- package/dist/src/thread-bindings.js +214 -0
- package/dist/src/tools/calendar-tool.js +2 -37
- package/dist/src/tools/call-phone-tool.js +3 -60
- package/dist/src/tools/create-alarm-tool.js +8 -109
- package/dist/src/tools/delete-alarm-tool.js +5 -69
- package/dist/src/tools/device-tool-map.d.ts +4 -0
- package/dist/src/tools/device-tool-map.js +24 -0
- package/dist/src/tools/image-reading-tool.d.ts +5 -0
- package/dist/src/tools/image-reading-tool.js +328 -0
- package/dist/src/tools/location-tool.js +6 -40
- package/dist/src/tools/modify-alarm-tool.js +8 -114
- package/dist/src/tools/modify-note-tool.js +3 -41
- package/dist/src/tools/note-tool.js +4 -16
- package/dist/src/tools/search-alarm-tool.js +12 -118
- package/dist/src/tools/search-calendar-tool.js +4 -81
- package/dist/src/tools/search-contact-tool.js +2 -55
- package/dist/src/tools/search-file-tool.js +4 -61
- package/dist/src/tools/search-message-tool.js +2 -59
- package/dist/src/tools/search-note-tool.js +4 -22
- package/dist/src/tools/search-photo-gallery-tool.js +8 -57
- package/dist/src/tools/send-command-to-car-tool.d.ts +5 -0
- package/dist/src/tools/send-command-to-car-tool.js +85 -0
- package/dist/src/tools/send-file-to-user-tool.js +1 -39
- package/dist/src/tools/send-message-tool.js +5 -56
- package/dist/src/tools/session-manager.d.ts +1 -0
- package/dist/src/tools/session-manager.js +0 -45
- package/dist/src/tools/timestamp-to-utc8-tool.d.ts +12 -0
- package/dist/src/tools/timestamp-to-utc8-tool.js +104 -0
- package/dist/src/tools/upload-file-tool.js +0 -49
- package/dist/src/tools/upload-photo-tool.js +0 -42
- package/dist/src/tools/view-push-result-tool.js +0 -11
- package/dist/src/tools/xiaoyi-collection-tool.js +4 -82
- package/dist/src/tools/xiaoyi-gui-tool.js +0 -34
- package/dist/src/utils/runtime-manager.d.ts +7 -0
- package/dist/src/utils/runtime-manager.js +42 -0
- package/dist/src/websocket.js +33 -13
- package/package.json +3 -4
- package/dist/src/tools/search-photo-tool.d.ts +0 -9
- package/dist/src/tools/search-photo-tool.js +0 -270
- package/dist/src/utils/session.d.ts +0 -34
- package/dist/src/utils/session.js +0 -50
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getXYWebSocketManager } from "../client.js";
|
|
2
2
|
import { XYFileUploadService } from "../file-upload.js";
|
|
3
3
|
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
|
-
import { logger } from "../utils/logger.js";
|
|
5
4
|
import fetch from "node-fetch";
|
|
6
5
|
import fs from "fs/promises";
|
|
7
6
|
import path from "path";
|
|
@@ -65,7 +64,6 @@ function normalizeToArray(param) {
|
|
|
65
64
|
* Download remote file to local temp directory
|
|
66
65
|
*/
|
|
67
66
|
async function downloadRemoteFile(url) {
|
|
68
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 📥 Downloading remote file: ${url}`);
|
|
69
67
|
try {
|
|
70
68
|
const response = await fetch(url);
|
|
71
69
|
if (!response.ok) {
|
|
@@ -88,11 +86,9 @@ async function downloadRemoteFile(url) {
|
|
|
88
86
|
const arrayBuffer = await response.arrayBuffer();
|
|
89
87
|
const buffer = Buffer.from(arrayBuffer);
|
|
90
88
|
await fs.writeFile(localPath, buffer);
|
|
91
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ File downloaded to: ${localPath}`);
|
|
92
89
|
return localPath;
|
|
93
90
|
}
|
|
94
91
|
catch (error) {
|
|
95
|
-
logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ Failed to download file from ${url}:`, error);
|
|
96
92
|
throw new Error(`Failed to download remote file: ${error instanceof Error ? error.message : String(error)}`);
|
|
97
93
|
}
|
|
98
94
|
}
|
|
@@ -136,54 +132,34 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
136
132
|
});
|
|
137
133
|
// Create the main execution promise
|
|
138
134
|
const executionPromise = (async () => {
|
|
139
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 🚀 Starting execution`);
|
|
140
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] - toolCallId: ${toolCallId}`);
|
|
141
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] - params (raw):`, JSON.stringify(params));
|
|
142
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
143
135
|
// Validate that at least one parameter is provided
|
|
144
136
|
if (!params.fileLocalUrls && !params.fileRemoteUrls) {
|
|
145
|
-
logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ Missing both fileLocalUrls and fileRemoteUrls parameters`);
|
|
146
137
|
throw new Error("At least one of fileLocalUrls or fileRemoteUrls must be provided");
|
|
147
138
|
}
|
|
148
139
|
// Normalize fileLocalUrls parameter
|
|
149
140
|
let fileLocalUrls = [];
|
|
150
141
|
if (params.fileLocalUrls) {
|
|
151
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 🔄 Normalizing fileLocalUrls parameter...`);
|
|
152
142
|
fileLocalUrls = normalizeToArray(params.fileLocalUrls);
|
|
153
143
|
if (fileLocalUrls.length === 0) {
|
|
154
|
-
logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ fileLocalUrls array is empty`);
|
|
155
144
|
throw new Error("fileLocalUrls array cannot be empty");
|
|
156
145
|
}
|
|
157
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ Normalized fileLocalUrls:`, JSON.stringify(fileLocalUrls));
|
|
158
146
|
}
|
|
159
147
|
// Normalize fileRemoteUrls parameter
|
|
160
148
|
let fileRemoteUrls = [];
|
|
161
149
|
if (params.fileRemoteUrls) {
|
|
162
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 🔄 Normalizing fileRemoteUrls parameter...`);
|
|
163
150
|
fileRemoteUrls = normalizeToArray(params.fileRemoteUrls);
|
|
164
151
|
if (fileRemoteUrls.length === 0) {
|
|
165
|
-
logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ fileRemoteUrls array is empty`);
|
|
166
152
|
throw new Error("fileRemoteUrls array cannot be empty");
|
|
167
153
|
}
|
|
168
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ Normalized fileRemoteUrls:`, JSON.stringify(fileRemoteUrls));
|
|
169
154
|
}
|
|
170
155
|
// Get session context
|
|
171
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 🔍 Attempting to get session context...`);
|
|
172
156
|
const sessionContext = getCurrentSessionContext();
|
|
173
157
|
if (!sessionContext) {
|
|
174
|
-
logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ FAILED: No active session found!`);
|
|
175
|
-
logger.error(`[SEND_FILE_TO_USER_TOOL] - toolCallId: ${toolCallId}`);
|
|
176
158
|
throw new Error("No active XY session found. Send file to user tool can only be used during an active conversation.");
|
|
177
159
|
}
|
|
178
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ Session context found`);
|
|
179
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
180
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
181
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
182
160
|
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
183
161
|
// Get WebSocket manager
|
|
184
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 🔌 Getting WebSocket manager...`);
|
|
185
162
|
const wsManager = getXYWebSocketManager(config);
|
|
186
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ WebSocket manager obtained`);
|
|
187
163
|
// Create upload service
|
|
188
164
|
const uploadService = new XYFileUploadService(config.fileUploadUrl, config.apiKey, config.uid);
|
|
189
165
|
// Collect all local file paths to upload
|
|
@@ -191,61 +167,48 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
191
167
|
const downloadedFiles = [];
|
|
192
168
|
// Download remote files to local temp directory
|
|
193
169
|
if (fileRemoteUrls.length > 0) {
|
|
194
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 📥 Downloading ${fileRemoteUrls.length} remote files...`);
|
|
195
170
|
for (let i = 0; i < fileRemoteUrls.length; i++) {
|
|
196
171
|
const remoteUrl = fileRemoteUrls[i];
|
|
197
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 📥 Downloading remote file ${i + 1}/${fileRemoteUrls.length}: ${remoteUrl}`);
|
|
198
172
|
try {
|
|
199
173
|
const localPath = await downloadRemoteFile(remoteUrl);
|
|
200
174
|
allLocalPaths.push(localPath);
|
|
201
175
|
downloadedFiles.push(localPath);
|
|
202
176
|
}
|
|
203
177
|
catch (error) {
|
|
204
|
-
logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ Failed to download file ${i + 1}:`, error);
|
|
205
178
|
throw new Error(`Failed to download remote file ${remoteUrl}: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
179
|
}
|
|
207
180
|
}
|
|
208
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ Downloaded ${downloadedFiles.length} remote files`);
|
|
209
181
|
}
|
|
210
182
|
// Upload all local files and get fileIds
|
|
211
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 📤 Uploading ${allLocalPaths.length} files...`);
|
|
212
183
|
const uploadedFiles = [];
|
|
213
184
|
for (let i = 0; i < allLocalPaths.length; i++) {
|
|
214
185
|
const localPath = allLocalPaths[i];
|
|
215
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 📤 Uploading file ${i + 1}/${allLocalPaths.length}: ${localPath}`);
|
|
216
186
|
try {
|
|
217
187
|
// Upload file using three-phase upload
|
|
218
188
|
const fileId = await uploadService.uploadFile(localPath);
|
|
219
189
|
if (!fileId) {
|
|
220
|
-
logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ Failed to upload file: ${localPath} (fileId is empty)`);
|
|
221
190
|
throw new Error(`Failed to upload file: ${localPath}`);
|
|
222
191
|
}
|
|
223
192
|
// Get filename and mime type
|
|
224
193
|
const fileName = localPath.split("/").pop() || "unknown";
|
|
225
194
|
const mimeType = getMimeTypeFromFilename(fileName);
|
|
226
195
|
uploadedFiles.push({ fileName, fileId, mimeType });
|
|
227
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ File uploaded successfully: ${fileName} -> ${fileId}`);
|
|
228
196
|
}
|
|
229
197
|
catch (error) {
|
|
230
|
-
logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ Failed to upload file ${i + 1}:`, error);
|
|
231
198
|
throw new Error(`Failed to upload file ${localPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
232
199
|
}
|
|
233
200
|
}
|
|
234
201
|
// Clean up downloaded files
|
|
235
202
|
if (downloadedFiles.length > 0) {
|
|
236
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 🧹 Cleaning up ${downloadedFiles.length} downloaded files...`);
|
|
237
203
|
for (const downloadedFile of downloadedFiles) {
|
|
238
204
|
try {
|
|
239
205
|
await fs.unlink(downloadedFile);
|
|
240
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ Cleaned up: ${downloadedFile}`);
|
|
241
206
|
}
|
|
242
207
|
catch (error) {
|
|
243
|
-
logger.warn(`[SEND_FILE_TO_USER_TOOL] ⚠️ Failed to clean up file ${downloadedFile}:`, error);
|
|
244
208
|
}
|
|
245
209
|
}
|
|
246
210
|
}
|
|
247
211
|
// Build and send agent_response messages for each file
|
|
248
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 📦 Building and sending agent_response messages...`);
|
|
249
212
|
const sentFiles = [];
|
|
250
213
|
for (const uploadedFile of uploadedFiles) {
|
|
251
214
|
const { fileName, fileId, mimeType } = uploadedFile;
|
|
@@ -281,10 +244,9 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
281
244
|
};
|
|
282
245
|
// Send WebSocket message
|
|
283
246
|
await wsManager.sendMessage(sessionId, agentResponse);
|
|
247
|
+
console.log(`send ${fileName} file to user success`);
|
|
284
248
|
sentFiles.push({ fileName, fileId });
|
|
285
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ Sent file to user: ${fileName} (fileId: ${fileId})`);
|
|
286
249
|
}
|
|
287
|
-
logger.log(`[SEND_FILE_TO_USER_TOOL] 🎉 Successfully sent ${sentFiles.length} files to user`);
|
|
288
250
|
return {
|
|
289
251
|
content: [
|
|
290
252
|
{
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getXYWebSocketManager } from "../client.js";
|
|
2
2
|
import { sendCommand } from "../formatter.js";
|
|
3
3
|
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
|
-
import { logger } from "../utils/logger.js";
|
|
5
4
|
/**
|
|
6
5
|
* XY send message tool - sends SMS message on user's device.
|
|
7
6
|
* Requires phoneNumber (with +86 prefix) and content parameters.
|
|
@@ -9,7 +8,7 @@ import { logger } from "../utils/logger.js";
|
|
|
9
8
|
export const sendMessageTool = {
|
|
10
9
|
name: "send_message",
|
|
11
10
|
label: "Send Message",
|
|
12
|
-
description: "通过手机发送短信。需要提供接收方手机号码和短信内容。手机号码会自动添加+86前缀(如果没有的话)。注意:操作超时时间为60
|
|
11
|
+
description: "通过手机发送短信。需要提供接收方手机号码和短信内容。手机号码会自动添加+86前缀(如果没有的话)。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。",
|
|
13
12
|
parameters: {
|
|
14
13
|
type: "object",
|
|
15
14
|
properties: {
|
|
@@ -25,18 +24,12 @@ export const sendMessageTool = {
|
|
|
25
24
|
required: ["phoneNumber", "content"],
|
|
26
25
|
},
|
|
27
26
|
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
27
|
// Validate phoneNumber parameter
|
|
33
28
|
if (!params.phoneNumber || typeof params.phoneNumber !== "string" || params.phoneNumber.trim() === "") {
|
|
34
|
-
logger.error(`[SEND_MESSAGE_TOOL] ❌ Missing or invalid phoneNumber parameter`);
|
|
35
29
|
throw new Error("Missing required parameter: phoneNumber must be a non-empty string");
|
|
36
30
|
}
|
|
37
31
|
// Validate content parameter
|
|
38
32
|
if (!params.content || typeof params.content !== "string" || params.content.trim() === "") {
|
|
39
|
-
logger.error(`[SEND_MESSAGE_TOOL] ❌ Missing or invalid content parameter`);
|
|
40
33
|
throw new Error("Missing required parameter: content must be a non-empty string");
|
|
41
34
|
}
|
|
42
35
|
// Normalize phone number: add +86 prefix if not present
|
|
@@ -51,30 +44,16 @@ export const sendMessageTool = {
|
|
|
51
44
|
phoneNumber = phoneNumber.substring(2);
|
|
52
45
|
}
|
|
53
46
|
phoneNumber = `+86${phoneNumber}`;
|
|
54
|
-
logger.log(`[SEND_MESSAGE_TOOL] 📞 Normalized phone number: ${params.phoneNumber} -> ${phoneNumber}`);
|
|
55
47
|
}
|
|
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
48
|
// Get session context
|
|
60
|
-
logger.log(`[SEND_MESSAGE_TOOL] 🔍 Attempting to get session context...`);
|
|
61
49
|
const sessionContext = getCurrentSessionContext();
|
|
62
50
|
if (!sessionContext) {
|
|
63
|
-
logger.error(`[SEND_MESSAGE_TOOL] ❌ FAILED: No active session found!`);
|
|
64
|
-
logger.error(`[SEND_MESSAGE_TOOL] - toolCallId: ${toolCallId}`);
|
|
65
51
|
throw new Error("No active XY session found. Send message tool can only be used during an active conversation.");
|
|
66
52
|
}
|
|
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
53
|
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
72
54
|
// Get WebSocket manager
|
|
73
|
-
logger.log(`[SEND_MESSAGE_TOOL] 🔌 Getting WebSocket manager...`);
|
|
74
55
|
const wsManager = getXYWebSocketManager(config);
|
|
75
|
-
logger.log(`[SEND_MESSAGE_TOOL] ✅ WebSocket manager obtained`);
|
|
76
56
|
// Build SendShortMessage command
|
|
77
|
-
logger.log(`[SEND_MESSAGE_TOOL] 📦 Building SendShortMessage command...`);
|
|
78
57
|
const command = {
|
|
79
58
|
header: {
|
|
80
59
|
namespace: "Common",
|
|
@@ -109,65 +88,37 @@ export const sendMessageTool = {
|
|
|
109
88
|
pageControlRelated: false,
|
|
110
89
|
},
|
|
111
90
|
};
|
|
112
|
-
logger.log(`[SEND_MESSAGE_TOOL] 📋 Command details:`, JSON.stringify(command, null, 2));
|
|
113
91
|
// 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
92
|
return new Promise((resolve, reject) => {
|
|
117
93
|
const timeout = setTimeout(() => {
|
|
118
|
-
logger.error(`[SEND_MESSAGE_TOOL] ⏰ Timeout: No response received within 60 seconds`);
|
|
119
94
|
wsManager.off("data-event", handler);
|
|
120
95
|
reject(new Error("发送短信超时(60秒)"));
|
|
121
96
|
}, 60000);
|
|
122
97
|
// Listen for data events from WebSocket
|
|
123
98
|
const handler = (event) => {
|
|
124
|
-
logger.log(`[SEND_MESSAGE_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
125
99
|
if (event.intentName === "SendShortMessage") {
|
|
126
|
-
logger.log(`[SEND_MESSAGE_TOOL] 🎯 SendShortMessage event received`);
|
|
127
|
-
logger.log(`[SEND_MESSAGE_TOOL] - status: ${event.status}`);
|
|
128
100
|
clearTimeout(timeout);
|
|
129
101
|
wsManager.off("data-event", handler);
|
|
130
102
|
if (event.status === "success" && event.outputs) {
|
|
131
|
-
|
|
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));
|
|
103
|
+
// 成功,直接返回完整的 event.outputs JSON 字符串
|
|
148
104
|
resolve({
|
|
149
105
|
content: [
|
|
150
106
|
{
|
|
151
107
|
type: "text",
|
|
152
|
-
text: JSON.stringify(
|
|
153
|
-
}
|
|
154
|
-
]
|
|
108
|
+
text: JSON.stringify(event.outputs),
|
|
109
|
+
}
|
|
110
|
+
]
|
|
155
111
|
});
|
|
156
112
|
}
|
|
157
113
|
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
114
|
const errorDetail = event.outputs ? JSON.stringify(event.outputs) : event.status;
|
|
162
115
|
reject(new Error(`发送短信失败: ${errorDetail}`));
|
|
163
116
|
}
|
|
164
117
|
}
|
|
165
118
|
};
|
|
166
119
|
// Register event handler
|
|
167
|
-
logger.log(`[SEND_MESSAGE_TOOL] 📡 Registering data-event handler on WebSocket manager`);
|
|
168
120
|
wsManager.on("data-event", handler);
|
|
169
121
|
// Send the command
|
|
170
|
-
logger.log(`[SEND_MESSAGE_TOOL] 📤 Sending SendShortMessage command...`);
|
|
171
122
|
sendCommand({
|
|
172
123
|
config,
|
|
173
124
|
sessionId,
|
|
@@ -176,10 +127,8 @@ export const sendMessageTool = {
|
|
|
176
127
|
command,
|
|
177
128
|
})
|
|
178
129
|
.then(() => {
|
|
179
|
-
logger.log(`[SEND_MESSAGE_TOOL] ✅ Command sent successfully, waiting for response...`);
|
|
180
130
|
})
|
|
181
131
|
.catch((error) => {
|
|
182
|
-
logger.error(`[SEND_MESSAGE_TOOL] ❌ Failed to send command:`, error);
|
|
183
132
|
clearTimeout(timeout);
|
|
184
133
|
wsManager.off("data-event", handler);
|
|
185
134
|
reject(error);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Session manager for XY tool context
|
|
2
2
|
// Stores active session contexts that tools can access
|
|
3
3
|
import { AsyncLocalStorage } from "async_hooks";
|
|
4
|
-
import { logger } from "../utils/logger.js";
|
|
5
4
|
import { configManager } from "../utils/config-manager.js";
|
|
6
5
|
import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
|
|
7
6
|
// Map of sessionKey -> SessionContextWithRef
|
|
@@ -13,19 +12,12 @@ const asyncLocalStorage = new AsyncLocalStorage();
|
|
|
13
12
|
* Should be called when starting to process a message.
|
|
14
13
|
*/
|
|
15
14
|
export function registerSession(sessionKey, context) {
|
|
16
|
-
logger.log(`[SESSION_MANAGER] 📝 Registering session: ${sessionKey}`);
|
|
17
|
-
logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
|
|
18
|
-
logger.log(`[SESSION_MANAGER] - taskId: ${context.taskId}`);
|
|
19
|
-
logger.log(`[SESSION_MANAGER] - messageId: ${context.messageId}`);
|
|
20
|
-
logger.log(`[SESSION_MANAGER] - agentId: ${context.agentId}`);
|
|
21
|
-
logger.log(`[SESSION_MANAGER] - Active sessions before: ${activeSessions.size}`);
|
|
22
15
|
const existing = activeSessions.get(sessionKey);
|
|
23
16
|
if (existing) {
|
|
24
17
|
// 更新上下文,增加引用计数
|
|
25
18
|
existing.taskId = context.taskId;
|
|
26
19
|
existing.messageId = context.messageId;
|
|
27
20
|
existing.refCount++;
|
|
28
|
-
logger.log(`[SESSION_MANAGER] - Updated existing, refCount=${existing.refCount}`);
|
|
29
21
|
}
|
|
30
22
|
else {
|
|
31
23
|
// 新建
|
|
@@ -33,45 +25,30 @@ export function registerSession(sessionKey, context) {
|
|
|
33
25
|
...context,
|
|
34
26
|
refCount: 1,
|
|
35
27
|
});
|
|
36
|
-
logger.log(`[SESSION_MANAGER] - Created new, refCount=1`);
|
|
37
28
|
}
|
|
38
|
-
logger.log(`[SESSION_MANAGER] - Active sessions after: ${activeSessions.size}`);
|
|
39
|
-
logger.log(`[SESSION_MANAGER] - All session keys: [${Array.from(activeSessions.keys()).join(", ")}]`);
|
|
40
29
|
}
|
|
41
30
|
/**
|
|
42
31
|
* Unregister a session context.
|
|
43
32
|
* Should be called when message processing is complete.
|
|
44
33
|
*/
|
|
45
34
|
export function unregisterSession(sessionKey) {
|
|
46
|
-
logger.log(`[SESSION_MANAGER] 🗑️ Unregistering session: ${sessionKey}`);
|
|
47
|
-
logger.log(`[SESSION_MANAGER] - Active sessions before: ${activeSessions.size}`);
|
|
48
|
-
logger.log(`[SESSION_MANAGER] - Session existed: ${activeSessions.has(sessionKey)}`);
|
|
49
35
|
const existing = activeSessions.get(sessionKey);
|
|
50
36
|
if (!existing) {
|
|
51
|
-
logger.log(`[SESSION_MANAGER] - Session not found`);
|
|
52
37
|
return;
|
|
53
38
|
}
|
|
54
39
|
existing.refCount--;
|
|
55
|
-
logger.log(`[SESSION_MANAGER] - Decremented refCount: ${existing.refCount}`);
|
|
56
40
|
if (existing.refCount <= 0) {
|
|
57
41
|
activeSessions.delete(sessionKey);
|
|
58
42
|
configManager.clearSession(existing.sessionId);
|
|
59
|
-
logger.log(`[SESSION_MANAGER] - Deleted (refCount=0)`);
|
|
60
43
|
}
|
|
61
|
-
logger.log(`[SESSION_MANAGER] - Active sessions after: ${activeSessions.size}`);
|
|
62
|
-
logger.log(`[SESSION_MANAGER] - Remaining session keys: [${Array.from(activeSessions.keys()).join(", ")}]`);
|
|
63
44
|
}
|
|
64
45
|
/**
|
|
65
46
|
* Get session context by sessionKey.
|
|
66
47
|
* Returns null if session not found.
|
|
67
48
|
*/
|
|
68
49
|
export function getSessionContext(sessionKey) {
|
|
69
|
-
logger.log(`[SESSION_MANAGER] 🔍 Getting session by key: ${sessionKey}`);
|
|
70
|
-
logger.log(`[SESSION_MANAGER] - Active sessions: ${activeSessions.size}`);
|
|
71
50
|
const contextWithRef = activeSessions.get(sessionKey) ?? null;
|
|
72
|
-
logger.log(`[SESSION_MANAGER] - Found: ${contextWithRef !== null}`);
|
|
73
51
|
if (contextWithRef) {
|
|
74
|
-
logger.log(`[SESSION_MANAGER] - sessionId: ${contextWithRef.sessionId}`);
|
|
75
52
|
// 返回时去掉refCount字段
|
|
76
53
|
const { refCount, ...context } = contextWithRef;
|
|
77
54
|
return context;
|
|
@@ -85,20 +62,12 @@ export function getSessionContext(sessionKey) {
|
|
|
85
62
|
* Returns null if no sessions are active.
|
|
86
63
|
*/
|
|
87
64
|
export function getLatestSessionContext() {
|
|
88
|
-
logger.log(`[SESSION_MANAGER] 🔍 Getting latest session context`);
|
|
89
|
-
logger.log(`[SESSION_MANAGER] - Active sessions count: ${activeSessions.size}`);
|
|
90
|
-
logger.log(`[SESSION_MANAGER] - Active session keys: [${Array.from(activeSessions.keys()).join(", ")}]`);
|
|
91
65
|
if (activeSessions.size === 0) {
|
|
92
|
-
logger.error(`[SESSION_MANAGER] - ❌ No active sessions found!`);
|
|
93
66
|
return null;
|
|
94
67
|
}
|
|
95
68
|
// Return the last added session
|
|
96
69
|
const sessions = Array.from(activeSessions.values());
|
|
97
70
|
const latestSessionWithRef = sessions[sessions.length - 1];
|
|
98
|
-
logger.log(`[SESSION_MANAGER] - ✅ Found latest session:`);
|
|
99
|
-
logger.log(`[SESSION_MANAGER] - sessionId: ${latestSessionWithRef.sessionId}`);
|
|
100
|
-
logger.log(`[SESSION_MANAGER] - taskId: ${latestSessionWithRef.taskId}`);
|
|
101
|
-
logger.log(`[SESSION_MANAGER] - messageId: ${latestSessionWithRef.messageId}`);
|
|
102
71
|
// 返回时去掉refCount字段
|
|
103
72
|
const { refCount, ...latestSession } = latestSessionWithRef;
|
|
104
73
|
return latestSession;
|
|
@@ -108,9 +77,6 @@ export function getLatestSessionContext() {
|
|
|
108
77
|
* This ensures thread-safe context isolation for concurrent requests.
|
|
109
78
|
*/
|
|
110
79
|
export function runWithSessionContext(context, callback) {
|
|
111
|
-
logger.log(`[SESSION_MANAGER] 🔐 Running with AsyncLocalStorage context`);
|
|
112
|
-
logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
|
|
113
|
-
logger.log(`[SESSION_MANAGER] - taskId: ${context.taskId}`);
|
|
114
80
|
return asyncLocalStorage.run(context, callback);
|
|
115
81
|
}
|
|
116
82
|
/**
|
|
@@ -125,7 +91,6 @@ export function getCurrentSessionContext() {
|
|
|
125
91
|
// 1. Get base context from AsyncLocalStorage
|
|
126
92
|
const context = asyncLocalStorage.getStore() ?? null;
|
|
127
93
|
if (!context) {
|
|
128
|
-
logger.warn(`[SESSION_MANAGER] ⚠️ No session context in AsyncLocalStorage`);
|
|
129
94
|
return null;
|
|
130
95
|
}
|
|
131
96
|
// 2. Get latest taskId and messageId from task-manager
|
|
@@ -133,12 +98,6 @@ export function getCurrentSessionContext() {
|
|
|
133
98
|
const latestMessageId = getCurrentMessageId(context.sessionId);
|
|
134
99
|
// 3. If task-manager has a newer taskId, use the latest value
|
|
135
100
|
if (latestTaskId && latestTaskId !== context.taskId) {
|
|
136
|
-
logger.log(`[SESSION_MANAGER] 🔄 TaskId updated (interruption detected)`);
|
|
137
|
-
logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
|
|
138
|
-
logger.log(`[SESSION_MANAGER] - Old taskId: ${context.taskId}`);
|
|
139
|
-
logger.log(`[SESSION_MANAGER] - New taskId: ${latestTaskId}`);
|
|
140
|
-
logger.log(`[SESSION_MANAGER] - Old messageId: ${context.messageId}`);
|
|
141
|
-
logger.log(`[SESSION_MANAGER] - New messageId: ${latestMessageId ?? context.messageId}`);
|
|
142
101
|
// Return updated context (create new object, don't modify original)
|
|
143
102
|
return {
|
|
144
103
|
...context,
|
|
@@ -147,9 +106,5 @@ export function getCurrentSessionContext() {
|
|
|
147
106
|
};
|
|
148
107
|
}
|
|
149
108
|
// 4. No update needed, return original context
|
|
150
|
-
logger.log(`[SESSION_MANAGER] ✅ Got current session context from AsyncLocalStorage`);
|
|
151
|
-
logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
|
|
152
|
-
logger.log(`[SESSION_MANAGER] - taskId: ${context.taskId}`);
|
|
153
|
-
logger.log(`[SESSION_MANAGER] - messageId: ${context.messageId}`);
|
|
154
109
|
return context;
|
|
155
110
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XY timestamp to UTC+8 time tool - converts timestamp to UTC+8 time format.
|
|
3
|
+
* Supports both second-level and millisecond-level timestamps.
|
|
4
|
+
*
|
|
5
|
+
* This is a utility tool that doesn't require device communication.
|
|
6
|
+
* It converts Unix timestamp to UTC+8 time in YYYYMMDD hhmmss format.
|
|
7
|
+
*
|
|
8
|
+
* Important: The search_calendar_event and search_alarm tools return timestamps.
|
|
9
|
+
* Call this tool first to convert timestamps to readable UTC+8 time format before
|
|
10
|
+
* presenting results to users or performing further operations.
|
|
11
|
+
*/
|
|
12
|
+
export declare const timestampToUtc8Tool: any;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XY timestamp to UTC+8 time tool - converts timestamp to UTC+8 time format.
|
|
3
|
+
* Supports both second-level and millisecond-level timestamps.
|
|
4
|
+
*
|
|
5
|
+
* This is a utility tool that doesn't require device communication.
|
|
6
|
+
* It converts Unix timestamp to UTC+8 time in YYYYMMDD hhmmss format.
|
|
7
|
+
*
|
|
8
|
+
* Important: The search_calendar_event and search_alarm tools return timestamps.
|
|
9
|
+
* Call this tool first to convert timestamps to readable UTC+8 time format before
|
|
10
|
+
* presenting results to users or performing further operations.
|
|
11
|
+
*/
|
|
12
|
+
export const timestampToUtc8Tool = {
|
|
13
|
+
name: "convert_timestamp_to_utc8_time",
|
|
14
|
+
label: "Convert Timestamp to UTC+8 Time",
|
|
15
|
+
description: `将时间戳转换为标准 UTC+8 时间格式。支持秒级时间戳和毫秒级时间戳。
|
|
16
|
+
|
|
17
|
+
输入参数:
|
|
18
|
+
- timestamp: 时间戳(数字类型),可以是秒级(10位)或毫秒级(13位)
|
|
19
|
+
|
|
20
|
+
输出格式:
|
|
21
|
+
- YYYYMMDD hhmmss(例如:20240315 143000 表示 2024年3月15日 14:30:00 北京时间)
|
|
22
|
+
|
|
23
|
+
重要说明:
|
|
24
|
+
搜索日程工具(search_calendar_event)和搜索闹钟工具(search_alarm)等工具中返回结果如果包含时间戳。
|
|
25
|
+
建议优先调用本时间戳转换工具,将时间戳转换为标准北京时间格式,再基于标准时间进行用户回答或下一步操作。
|
|
26
|
+
|
|
27
|
+
示例:
|
|
28
|
+
- 输入:1710498600(秒级)或 1710498600000(毫秒级)
|
|
29
|
+
- 输出:20240315 143000`,
|
|
30
|
+
parameters: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
timestamp: {
|
|
34
|
+
type: "number",
|
|
35
|
+
description: "时间戳,支持秒级(10位)或毫秒级(13位)时间戳",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
required: ["timestamp"],
|
|
39
|
+
},
|
|
40
|
+
async execute(toolCallId, params) {
|
|
41
|
+
// Validate timestamp parameter
|
|
42
|
+
if (params.timestamp === undefined || params.timestamp === null) {
|
|
43
|
+
throw new Error("缺少必需参数:timestamp");
|
|
44
|
+
}
|
|
45
|
+
const timestamp = params.timestamp;
|
|
46
|
+
// Validate it's a number
|
|
47
|
+
if (typeof timestamp !== "number") {
|
|
48
|
+
throw new Error("timestamp 必须是数字类型");
|
|
49
|
+
}
|
|
50
|
+
// Check if timestamp is valid
|
|
51
|
+
if (isNaN(timestamp)) {
|
|
52
|
+
throw new Error("timestamp 不是有效数字");
|
|
53
|
+
}
|
|
54
|
+
// Determine if it's seconds or milliseconds
|
|
55
|
+
// Millisecond timestamps are typically 13 digits (year 2024+)
|
|
56
|
+
// Second timestamps are typically 10 digits
|
|
57
|
+
let timestampInMs;
|
|
58
|
+
const timestampStr = Math.abs(timestamp).toString();
|
|
59
|
+
if (timestampStr.length === 13) {
|
|
60
|
+
// It's already in milliseconds
|
|
61
|
+
timestampInMs = timestamp;
|
|
62
|
+
}
|
|
63
|
+
else if (timestampStr.length === 10) {
|
|
64
|
+
// It's in seconds, convert to milliseconds
|
|
65
|
+
timestampInMs = timestamp * 1000;
|
|
66
|
+
}
|
|
67
|
+
else if (timestamp > 1000000000000) {
|
|
68
|
+
// Likely milliseconds (greater than year 2001 in milliseconds)
|
|
69
|
+
timestampInMs = timestamp;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Likely seconds, convert to milliseconds
|
|
73
|
+
timestampInMs = timestamp * 1000;
|
|
74
|
+
}
|
|
75
|
+
// Create Date object from timestamp
|
|
76
|
+
const date = new Date(timestampInMs);
|
|
77
|
+
// Check if date is valid
|
|
78
|
+
if (isNaN(date.getTime())) {
|
|
79
|
+
throw new Error("无效的时间戳,无法转换为日期");
|
|
80
|
+
}
|
|
81
|
+
// Convert to Beijing time (UTC+8)
|
|
82
|
+
const beijingTime = new Date(date.getTime() + (8 * 60 * 60 * 1000));
|
|
83
|
+
// Format: YYYYMMDD hhmmss
|
|
84
|
+
const year = beijingTime.getUTCFullYear();
|
|
85
|
+
const month = beijingTime.getUTCMonth() + 1; // Months are 0-indexed
|
|
86
|
+
const day = beijingTime.getUTCDate();
|
|
87
|
+
const hours = beijingTime.getUTCHours();
|
|
88
|
+
const minutes = beijingTime.getUTCMinutes();
|
|
89
|
+
const seconds = beijingTime.getUTCSeconds();
|
|
90
|
+
// Pad with leading zeros
|
|
91
|
+
const formattedDate = `${year}${String(month).padStart(2, '0')}${String(day).padStart(2, '0')}`;
|
|
92
|
+
const formattedTime = `${String(hours).padStart(2, '0')}${String(minutes).padStart(2, '0')}${String(seconds).padStart(2, '0')}`;
|
|
93
|
+
const result = `${formattedDate} ${formattedTime}`;
|
|
94
|
+
// Return the formatted time
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: result,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
};
|