@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.
Files changed (33) hide show
  1. package/dist/src/bot.js +13 -3
  2. package/dist/src/channel.js +10 -1
  3. package/dist/src/tools/calendar-tool.js +2 -2
  4. package/dist/src/tools/call-phone-tool.d.ts +5 -0
  5. package/dist/src/tools/call-phone-tool.js +183 -0
  6. package/dist/src/tools/create-alarm-tool.d.ts +7 -0
  7. package/dist/src/tools/create-alarm-tool.js +395 -0
  8. package/dist/src/tools/delete-alarm-tool.d.ts +11 -0
  9. package/dist/src/tools/delete-alarm-tool.js +238 -0
  10. package/dist/src/tools/location-tool.js +2 -2
  11. package/dist/src/tools/modify-alarm-tool.d.ts +9 -0
  12. package/dist/src/tools/modify-alarm-tool.js +433 -0
  13. package/dist/src/tools/modify-note-tool.js +2 -2
  14. package/dist/src/tools/note-tool.js +2 -2
  15. package/dist/src/tools/search-alarm-tool.d.ts +8 -0
  16. package/dist/src/tools/search-alarm-tool.js +389 -0
  17. package/dist/src/tools/search-calendar-tool.js +2 -2
  18. package/dist/src/tools/search-contact-tool.js +2 -2
  19. package/dist/src/tools/search-file-tool.d.ts +5 -0
  20. package/dist/src/tools/search-file-tool.js +173 -0
  21. package/dist/src/tools/search-message-tool.d.ts +5 -0
  22. package/dist/src/tools/search-message-tool.js +173 -0
  23. package/dist/src/tools/search-note-tool.js +2 -2
  24. package/dist/src/tools/search-photo-gallery-tool.js +2 -2
  25. package/dist/src/tools/send-message-tool.d.ts +5 -0
  26. package/dist/src/tools/send-message-tool.js +189 -0
  27. package/dist/src/tools/session-manager.d.ts +12 -0
  28. package/dist/src/tools/session-manager.js +33 -0
  29. package/dist/src/tools/upload-file-tool.d.ts +13 -0
  30. package/dist/src/tools/upload-file-tool.js +265 -0
  31. package/dist/src/tools/upload-photo-tool.js +2 -2
  32. package/dist/src/tools/xiaoyi-gui-tool.js +2 -2
  33. package/package.json +1 -1
@@ -0,0 +1,265 @@
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 upload file tool - uploads local files to get publicly accessible URLs.
7
+ * Requires file URIs from search_file tool as prerequisite.
8
+ *
9
+ * Prerequisites:
10
+ * 1. Call search_file tool first to get URIs of files
11
+ * 2. Use the URIs to get public URLs
12
+ *
13
+ * Usage Note:
14
+ * - After getting public URLs, if further processing is needed, download the file first
15
+ * - URLs returned are publicly accessible
16
+ */
17
+ export const uploadFileTool = {
18
+ name: "upload_file",
19
+ label: "Upload File",
20
+ description: `工具能力描述:将手机本地文件上传并获取可公网访问的 URL。
21
+
22
+ 前置工具调用:此工具使用前必须先调用 search_file 工具获取文件的 uri
23
+
24
+ 工具参数说明:
25
+ a. 入参中的fileInfos数组,每个元素必须包含mediaUri字段(对应于search_file工具返回结果中的uri),必须与search_file结果中对应的uri完全保持一致,不要自行修改。
26
+ b. fileInfos中的timeout字段是可选的,表示上传文件超时时间,单位是毫秒,默认是20000(20秒)。
27
+ c. fileInfos 是文件在手机本地的信息数组(从 search_file 工具响应中获取)。限制:每次最多支持传入 5 条文件信息。
28
+
29
+ 注意事项:
30
+ a. 操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。
31
+ b. 此工具返回的文件链接为用户公网可访问的链接,如果需要对文件进行额外的操作,需要先根据返回的url下载文件,然后进行下一步处理。`,
32
+ parameters: {
33
+ type: "object",
34
+ properties: {
35
+ fileInfos: {
36
+ // 不指定 type,允许传入数组或 JSON 字符串
37
+ // 具体的类型验证和转换在 execute 函数内部进行
38
+ description: "文件信息数组,每个元素包含mediaUri(必需)和timeout(可选,默认20000)。必须先通过 search_file 工具获取。每次最多支持 5 条文件信息。",
39
+ },
40
+ },
41
+ required: ["fileInfos"],
42
+ },
43
+ async execute(toolCallId, params) {
44
+ logger.log(`[UPLOAD_FILE_TOOL] 🚀 Starting execution`);
45
+ logger.log(`[UPLOAD_FILE_TOOL] - toolCallId: ${toolCallId}`);
46
+ logger.log(`[UPLOAD_FILE_TOOL] - params (raw):`, JSON.stringify(params));
47
+ logger.log(`[UPLOAD_FILE_TOOL] - params.fileInfos type:`, typeof params.fileInfos);
48
+ logger.log(`[UPLOAD_FILE_TOOL] - timestamp: ${new Date().toISOString()}`);
49
+ // ===== 参数规范化:兼容数组和 JSON 字符串 =====
50
+ let fileInfos = null;
51
+ if (!params.fileInfos) {
52
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ Missing parameter: fileInfos`);
53
+ throw new Error("Missing required parameter: fileInfos");
54
+ }
55
+ // 情况1: 已经是数组
56
+ if (Array.isArray(params.fileInfos)) {
57
+ logger.log(`[UPLOAD_FILE_TOOL] ✅ fileInfos is already an array`);
58
+ fileInfos = params.fileInfos;
59
+ }
60
+ // 情况2: 是字符串,尝试解析为 JSON 数组
61
+ else if (typeof params.fileInfos === 'string') {
62
+ logger.log(`[UPLOAD_FILE_TOOL] 🔄 fileInfos is a string, attempting to parse as JSON...`);
63
+ try {
64
+ const parsed = JSON.parse(params.fileInfos);
65
+ if (Array.isArray(parsed)) {
66
+ logger.log(`[UPLOAD_FILE_TOOL] ✅ Successfully parsed JSON string to array`);
67
+ fileInfos = parsed;
68
+ }
69
+ else {
70
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ Parsed JSON is not an array:`, typeof parsed);
71
+ throw new Error("fileInfos must be an array or a JSON string representing an array");
72
+ }
73
+ }
74
+ catch (parseError) {
75
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ Failed to parse fileInfos as JSON:`, parseError);
76
+ throw new Error(`fileInfos must be a valid JSON array string. Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
77
+ }
78
+ }
79
+ // 情况3: 其他类型,报错
80
+ else {
81
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ Invalid fileInfos type:`, typeof params.fileInfos);
82
+ throw new Error(`fileInfos must be an array or a JSON string, got ${typeof params.fileInfos}`);
83
+ }
84
+ // 验证数组非空
85
+ if (!fileInfos || fileInfos.length === 0) {
86
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ fileInfos array is empty`);
87
+ throw new Error("fileInfos array cannot be empty");
88
+ }
89
+ logger.log(`[UPLOAD_FILE_TOOL] ✅ Normalized fileInfos:`, JSON.stringify(fileInfos));
90
+ // Validate maximum 5 file infos
91
+ if (fileInfos.length > 5) {
92
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ Too many fileInfos: ${fileInfos.length}`);
93
+ throw new Error(`最多支持 5 条文件信息,当前提供了 ${fileInfos.length} 条。请分批处理。`);
94
+ }
95
+ // Validate each fileInfo has required mediaUri field
96
+ for (let i = 0; i < fileInfos.length; i++) {
97
+ const fileInfo = fileInfos[i];
98
+ if (!fileInfo || typeof fileInfo !== 'object') {
99
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ fileInfo at index ${i} is not an object`);
100
+ throw new Error(`fileInfos[${i}] must be an object with mediaUri property`);
101
+ }
102
+ if (!fileInfo.mediaUri || typeof fileInfo.mediaUri !== 'string') {
103
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ fileInfo at index ${i} missing or invalid mediaUri`);
104
+ throw new Error(`fileInfos[${i}] must have a valid mediaUri string property`);
105
+ }
106
+ // Set default timeout if not provided
107
+ if (!fileInfo.timeout) {
108
+ fileInfo.timeout = "20000";
109
+ }
110
+ }
111
+ logger.log(`[UPLOAD_FILE_TOOL] - fileInfos count: ${fileInfos.length}`);
112
+ // Get session context
113
+ logger.log(`[UPLOAD_FILE_TOOL] 🔍 Attempting to get session context...`);
114
+ const sessionContext = getCurrentSessionContext();
115
+ if (!sessionContext) {
116
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ FAILED: No active session found!`);
117
+ logger.error(`[UPLOAD_FILE_TOOL] - toolCallId: ${toolCallId}`);
118
+ throw new Error("No active XY session found. Upload file tool can only be used during an active conversation.");
119
+ }
120
+ logger.log(`[UPLOAD_FILE_TOOL] ✅ Session context found`);
121
+ logger.log(`[UPLOAD_FILE_TOOL] - sessionId: ${sessionContext.sessionId}`);
122
+ logger.log(`[UPLOAD_FILE_TOOL] - taskId: ${sessionContext.taskId}`);
123
+ logger.log(`[UPLOAD_FILE_TOOL] - messageId: ${sessionContext.messageId}`);
124
+ const { config, sessionId, taskId, messageId } = sessionContext;
125
+ // Get WebSocket manager
126
+ logger.log(`[UPLOAD_FILE_TOOL] 🔌 Getting WebSocket manager...`);
127
+ const wsManager = getXYWebSocketManager(config);
128
+ logger.log(`[UPLOAD_FILE_TOOL] ✅ WebSocket manager obtained`);
129
+ // Get public URLs for the files
130
+ logger.log(`[UPLOAD_FILE_TOOL] 🌐 Getting public URLs for files...`);
131
+ const fileUrls = await getFileUrls(wsManager, config, sessionId, taskId, messageId, fileInfos);
132
+ logger.log(`[UPLOAD_FILE_TOOL] 🎉 Successfully retrieved ${fileUrls.length} file URLs`);
133
+ return {
134
+ content: [
135
+ {
136
+ type: "text",
137
+ text: JSON.stringify({
138
+ fileUrls,
139
+ count: fileUrls.length,
140
+ message: `成功获取 ${fileUrls.length} 个文件的公网访问 URL`
141
+ }),
142
+ },
143
+ ],
144
+ };
145
+ },
146
+ };
147
+ /**
148
+ * Get public URLs for files using fileInfos
149
+ * Returns array of publicly accessible file URLs
150
+ */
151
+ async function getFileUrls(wsManager, config, sessionId, taskId, messageId, fileInfos) {
152
+ logger.log(`[UPLOAD_FILE_TOOL] 📦 Building FileUploadForClaw command...`);
153
+ const command = {
154
+ header: {
155
+ namespace: "Common",
156
+ name: "Action",
157
+ },
158
+ payload: {
159
+ cardParam: {},
160
+ executeParam: {
161
+ executeMode: "background",
162
+ intentName: "FileUploadForClaw",
163
+ bundleName: "com.huawei.hmos.vassistant",
164
+ needUnlock: true,
165
+ actionResponse: true,
166
+ appType: "OHOS_APP",
167
+ timeOut: 5,
168
+ intentParam: {
169
+ fileInfos: fileInfos,
170
+ },
171
+ permissionId: [],
172
+ achieveType: "INTENT",
173
+ },
174
+ responses: [
175
+ {
176
+ resultCode: "",
177
+ displayText: "",
178
+ ttsText: "",
179
+ },
180
+ ],
181
+ needUploadResult: true,
182
+ noHalfPage: false,
183
+ pageControlRelated: false,
184
+ },
185
+ };
186
+ return new Promise((resolve, reject) => {
187
+ const timeout = setTimeout(() => {
188
+ logger.error(`[UPLOAD_FILE_TOOL] ⏰ Timeout: No response for FileUploadForClaw within 60 seconds`);
189
+ wsManager.off("data-event", handler);
190
+ reject(new Error("获取文件URL超时(60秒)"));
191
+ }, 60000);
192
+ const handler = (event) => {
193
+ logger.log(`[UPLOAD_FILE_TOOL] 📨 Received data event:`, JSON.stringify(event));
194
+ if (event.intentName === "FileUploadForClaw") {
195
+ logger.log(`[UPLOAD_FILE_TOOL] 🎯 FileUploadForClaw event received`);
196
+ logger.log(`[UPLOAD_FILE_TOOL] - status: ${event.status}`);
197
+ clearTimeout(timeout);
198
+ wsManager.off("data-event", handler);
199
+ if (event.status === "success" && event.outputs) {
200
+ logger.log(`[UPLOAD_FILE_TOOL] ✅ File URL retrieval completed successfully`);
201
+ // Check for error code in outputs
202
+ const code = event.outputs.code !== undefined ? event.outputs.code : null;
203
+ if (code !== null && code !== 0) {
204
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ Device returned error`);
205
+ logger.error(`[UPLOAD_FILE_TOOL] - code: ${code}`);
206
+ const errorMsg = event.outputs.errorMsg || event.outputs.errMsg || "未知错误";
207
+ logger.error(`[UPLOAD_FILE_TOOL] - errorMsg: ${errorMsg}`);
208
+ reject(new Error(`获取文件URL失败: ${errorMsg} (错误代码: ${code})`));
209
+ return;
210
+ }
211
+ // Safe navigation through outputs structure
212
+ const outputs = event.outputs;
213
+ const result = outputs?.result;
214
+ let fileUrls = [];
215
+ if (result && typeof result === 'object') {
216
+ const urls = result.fileUrls;
217
+ if (Array.isArray(urls)) {
218
+ fileUrls = urls;
219
+ }
220
+ }
221
+ // Decode Unicode escape sequences in URLs
222
+ // Replace \u003d with = and \u0026 with &
223
+ fileUrls = fileUrls.map((url) => {
224
+ if (typeof url !== 'string') {
225
+ logger.warn(`[UPLOAD_FILE_TOOL] ⚠️ URL is not a string:`, typeof url);
226
+ return '';
227
+ }
228
+ const decodedUrl = url
229
+ .replace(/\\u003d/g, '=')
230
+ .replace(/\\u0026/g, '&');
231
+ logger.log(`[UPLOAD_FILE_TOOL] 🔄 Decoded URL: ${url} -> ${decodedUrl}`);
232
+ return decodedUrl;
233
+ }).filter((url) => url.length > 0);
234
+ logger.log(`[UPLOAD_FILE_TOOL] 📊 Retrieved and decoded ${fileUrls.length} file URLs`);
235
+ resolve(fileUrls);
236
+ }
237
+ else {
238
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ File URL retrieval failed`);
239
+ logger.error(`[UPLOAD_FILE_TOOL] - status: ${event.status}`);
240
+ logger.error(`[UPLOAD_FILE_TOOL] - outputs:`, JSON.stringify(event.outputs || {}));
241
+ reject(new Error(`获取文件URL失败: ${event.status}`));
242
+ }
243
+ }
244
+ };
245
+ logger.log(`[UPLOAD_FILE_TOOL] 📡 Registering data-event handler for FileUploadForClaw`);
246
+ wsManager.on("data-event", handler);
247
+ logger.log(`[UPLOAD_FILE_TOOL] 📤 Sending FileUploadForClaw command...`);
248
+ sendCommand({
249
+ config,
250
+ sessionId,
251
+ taskId,
252
+ messageId,
253
+ command,
254
+ })
255
+ .then(() => {
256
+ logger.log(`[UPLOAD_FILE_TOOL] ✅ FileUploadForClaw command sent successfully`);
257
+ })
258
+ .catch((error) => {
259
+ logger.error(`[UPLOAD_FILE_TOOL] ❌ Failed to send FileUploadForClaw command:`, error);
260
+ clearTimeout(timeout);
261
+ wsManager.off("data-event", handler);
262
+ reject(error);
263
+ });
264
+ });
265
+ }
@@ -1,6 +1,6 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { sendCommand } from "../formatter.js";
3
- import { getLatestSessionContext } from "./session-manager.js";
3
+ import { getCurrentSessionContext } from "./session-manager.js";
4
4
  import { logger } from "../utils/logger.js";
5
5
  /**
6
6
  * XY upload photo tool - uploads local photos to get publicly accessible URLs.
@@ -90,7 +90,7 @@ export const uploadPhotoTool = {
90
90
  logger.log(`[UPLOAD_PHOTO_TOOL] - mediaUris count: ${mediaUris.length}`);
91
91
  // Get session context
92
92
  logger.log(`[UPLOAD_PHOTO_TOOL] 🔍 Attempting to get session context...`);
93
- const sessionContext = getLatestSessionContext();
93
+ const sessionContext = getCurrentSessionContext();
94
94
  if (!sessionContext) {
95
95
  logger.error(`[UPLOAD_PHOTO_TOOL] ❌ FAILED: No active session found!`);
96
96
  logger.error(`[UPLOAD_PHOTO_TOOL] - toolCallId: ${toolCallId}`);
@@ -1,7 +1,7 @@
1
1
  // XiaoYi GUI tool implementation - simulates phone screen interactions
2
2
  import { getXYWebSocketManager } from "../client.js";
3
3
  import { sendCommand } from "../formatter.js";
4
- import { getLatestSessionContext } from "./session-manager.js";
4
+ import { getCurrentSessionContext } from "./session-manager.js";
5
5
  import { logger } from "../utils/logger.js";
6
6
  /**
7
7
  * XiaoYi GUI tool - executes phone app interactions through GUI agent.
@@ -49,7 +49,7 @@ export const xiaoyiGuiTool = {
49
49
  }
50
50
  // Get session context
51
51
  logger.log(`[XIAOYI_GUI_TOOL] 🔍 Attempting to get session context...`);
52
- const sessionContext = getLatestSessionContext();
52
+ const sessionContext = getCurrentSessionContext();
53
53
  if (!sessionContext) {
54
54
  logger.error(`[XIAOYI_GUI_TOOL] ❌ FAILED: No active session found!`);
55
55
  logger.error(`[XIAOYI_GUI_TOOL] - toolCallId: ${toolCallId}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.26-beta",
3
+ "version": "0.0.27-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",