@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.
Files changed (73) hide show
  1. package/dist/index.d.ts +0 -2
  2. package/dist/index.js +42 -2
  3. package/dist/src/bot.d.ts +1 -0
  4. package/dist/src/bot.js +59 -61
  5. package/dist/src/channel.js +30 -4
  6. package/dist/src/client.js +0 -9
  7. package/dist/src/cspl/call-api.d.ts +3 -0
  8. package/dist/src/cspl/call-api.js +86 -0
  9. package/dist/src/cspl/config.d.ts +19 -0
  10. package/dist/src/cspl/config.js +50 -0
  11. package/dist/src/cspl/constants.d.ts +43 -0
  12. package/dist/src/cspl/constants.js +22 -0
  13. package/dist/src/cspl/utils.d.ts +10 -0
  14. package/dist/src/cspl/utils.js +57 -0
  15. package/dist/src/file-upload.d.ts +5 -0
  16. package/dist/src/file-upload.js +88 -6
  17. package/dist/src/formatter.d.ts +2 -0
  18. package/dist/src/formatter.js +13 -30
  19. package/dist/src/heartbeat.js +0 -4
  20. package/dist/src/monitor.js +8 -10
  21. package/dist/src/onboarding.d.ts +3 -4
  22. package/dist/src/onboarding.js +2 -2
  23. package/dist/src/outbound.d.ts +2 -1
  24. package/dist/src/outbound.js +1 -19
  25. package/dist/src/parser.d.ts +6 -0
  26. package/dist/src/parser.js +16 -0
  27. package/dist/src/push.js +0 -21
  28. package/dist/src/reply-dispatcher.d.ts +4 -0
  29. package/dist/src/reply-dispatcher.js +46 -8
  30. package/dist/src/steer-injector.d.ts +16 -0
  31. package/dist/src/steer-injector.js +74 -0
  32. package/dist/src/thread-bindings.d.ts +54 -0
  33. package/dist/src/thread-bindings.js +214 -0
  34. package/dist/src/tools/calendar-tool.js +2 -37
  35. package/dist/src/tools/call-phone-tool.js +3 -60
  36. package/dist/src/tools/create-alarm-tool.js +8 -109
  37. package/dist/src/tools/delete-alarm-tool.js +5 -69
  38. package/dist/src/tools/device-tool-map.d.ts +4 -0
  39. package/dist/src/tools/device-tool-map.js +24 -0
  40. package/dist/src/tools/image-reading-tool.d.ts +5 -0
  41. package/dist/src/tools/image-reading-tool.js +328 -0
  42. package/dist/src/tools/location-tool.js +6 -40
  43. package/dist/src/tools/modify-alarm-tool.js +8 -114
  44. package/dist/src/tools/modify-note-tool.js +3 -41
  45. package/dist/src/tools/note-tool.js +4 -16
  46. package/dist/src/tools/search-alarm-tool.js +12 -118
  47. package/dist/src/tools/search-calendar-tool.js +4 -81
  48. package/dist/src/tools/search-contact-tool.js +2 -55
  49. package/dist/src/tools/search-file-tool.js +4 -61
  50. package/dist/src/tools/search-message-tool.js +2 -59
  51. package/dist/src/tools/search-note-tool.js +4 -22
  52. package/dist/src/tools/search-photo-gallery-tool.js +8 -57
  53. package/dist/src/tools/send-command-to-car-tool.d.ts +5 -0
  54. package/dist/src/tools/send-command-to-car-tool.js +85 -0
  55. package/dist/src/tools/send-file-to-user-tool.js +1 -39
  56. package/dist/src/tools/send-message-tool.js +5 -56
  57. package/dist/src/tools/session-manager.d.ts +1 -0
  58. package/dist/src/tools/session-manager.js +0 -45
  59. package/dist/src/tools/timestamp-to-utc8-tool.d.ts +12 -0
  60. package/dist/src/tools/timestamp-to-utc8-tool.js +104 -0
  61. package/dist/src/tools/upload-file-tool.js +0 -49
  62. package/dist/src/tools/upload-photo-tool.js +0 -42
  63. package/dist/src/tools/view-push-result-tool.js +0 -11
  64. package/dist/src/tools/xiaoyi-collection-tool.js +4 -82
  65. package/dist/src/tools/xiaoyi-gui-tool.js +0 -34
  66. package/dist/src/utils/runtime-manager.d.ts +7 -0
  67. package/dist/src/utils/runtime-manager.js +42 -0
  68. package/dist/src/websocket.js +33 -13
  69. package/package.json +3 -4
  70. package/dist/src/tools/search-photo-tool.d.ts +0 -9
  71. package/dist/src/tools/search-photo-tool.js +0 -270
  72. package/dist/src/utils/session.d.ts +0 -34
  73. 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
- 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));
103
+ // 成功,直接返回完整的 event.outputs JSON 字符串
148
104
  resolve({
149
105
  content: [
150
106
  {
151
107
  type: "text",
152
- text: JSON.stringify(result),
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);
@@ -5,6 +5,7 @@ export interface SessionContext {
5
5
  taskId: string;
6
6
  messageId: string;
7
7
  agentId: string;
8
+ deviceType?: string;
8
9
  }
9
10
  /**
10
11
  * Register a session context for tool access.
@@ -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
+ };