@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,389 @@
|
|
|
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
|
+
// Enum definitions for alarm search parameters
|
|
6
|
+
const RANGE_TYPE_VALUES = ["all", "next", "current"];
|
|
7
|
+
const ALARM_STATE_VALUES = [0, 1];
|
|
8
|
+
const DAYS_OF_WAKE_TYPE_VALUES = [0, 1, 2, 3, 4];
|
|
9
|
+
/**
|
|
10
|
+
* XY search alarm tool - searches alarms on user's device.
|
|
11
|
+
* Returns matching alarms based on various filter criteria.
|
|
12
|
+
*
|
|
13
|
+
* At least one search criterion must be provided.
|
|
14
|
+
* Multiple criteria can be combined.
|
|
15
|
+
*/
|
|
16
|
+
export const searchAlarmTool = {
|
|
17
|
+
name: "search_alarm",
|
|
18
|
+
label: "Search Alarm",
|
|
19
|
+
description: `检索用户设备上的闹钟。支持多种检索条件,至少需要提供一个检索条件,多个条件可以组合使用。
|
|
20
|
+
|
|
21
|
+
检索条件(至少提供一个):
|
|
22
|
+
- rangeType: 查询范围,枚举值:all=查询所有闹钟,next=查找下一个响铃闹钟,current=一小时内最近一次增查改的闹钟
|
|
23
|
+
- alarmState: 闹钟开启状态,0=关闭,1=开启
|
|
24
|
+
- daysOfWakeType: 闹钟响铃类型,0=单次响铃,1=法定节假日,2=每天,3=自定义时间,4=法定工作日
|
|
25
|
+
- startTime: 时间间隔开始,格式 YYYYMMDD hhmmss(例如:20240315 000000),需要与 endTime 一起使用
|
|
26
|
+
- endTime: 时间间隔结束,格式 YYYYMMDD hhmmss(例如:20240315 235959),需要与 startTime 一起使用
|
|
27
|
+
|
|
28
|
+
使用示例:
|
|
29
|
+
- 查询所有闹钟:{"rangeType": "all"}
|
|
30
|
+
- 查询已开启的闹钟:{"alarmState": 1}
|
|
31
|
+
- 查询每天响铃的闹钟:{"daysOfWakeType": 2}
|
|
32
|
+
- 查询某个时间段的闹钟:{"startTime": "20240315 000000", "endTime": "20240315 235959"}
|
|
33
|
+
|
|
34
|
+
注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。`,
|
|
35
|
+
parameters: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
rangeType: {
|
|
39
|
+
type: "string",
|
|
40
|
+
enum: ["all", "next", "current"],
|
|
41
|
+
description: "查询范围:all=所有闹钟,next=下一个响铃闹钟,current=一小时内最近修改的闹钟",
|
|
42
|
+
},
|
|
43
|
+
alarmState: {
|
|
44
|
+
type: "number",
|
|
45
|
+
enum: [0, 1],
|
|
46
|
+
description: "闹钟开启状态:0=关闭,1=开启",
|
|
47
|
+
},
|
|
48
|
+
daysOfWakeType: {
|
|
49
|
+
type: "number",
|
|
50
|
+
enum: [0, 1, 2, 3, 4],
|
|
51
|
+
description: "闹钟响铃类型:0=单次,1=法定节假日,2=每天,3=自定义,4=法定工作日",
|
|
52
|
+
},
|
|
53
|
+
startTime: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "时间间隔开始,格式 YYYYMMDD hhmmss(例如:20240315 000000),必须与 endTime 一起使用",
|
|
56
|
+
},
|
|
57
|
+
endTime: {
|
|
58
|
+
type: "string",
|
|
59
|
+
description: "时间间隔结束,格式 YYYYMMDD hhmmss(例如:20240315 235959),必须与 startTime 一起使用",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
async execute(toolCallId, params) {
|
|
64
|
+
logger.log(`[SEARCH_ALARM_TOOL] 🚀 Starting execution`);
|
|
65
|
+
logger.log(`[SEARCH_ALARM_TOOL] - toolCallId: ${toolCallId}`);
|
|
66
|
+
logger.log(`[SEARCH_ALARM_TOOL] - params:`, JSON.stringify(params));
|
|
67
|
+
logger.log(`[SEARCH_ALARM_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
68
|
+
// ===== Validate at least one search criterion is provided =====
|
|
69
|
+
const hasRangeType = params.rangeType !== undefined && params.rangeType !== null;
|
|
70
|
+
const hasAlarmState = params.alarmState !== undefined && params.alarmState !== null;
|
|
71
|
+
const hasDaysOfWakeType = params.daysOfWakeType !== undefined && params.daysOfWakeType !== null;
|
|
72
|
+
const hasStartTime = params.startTime !== undefined && params.startTime !== null;
|
|
73
|
+
const hasEndTime = params.endTime !== undefined && params.endTime !== null;
|
|
74
|
+
if (!hasRangeType && !hasAlarmState && !hasDaysOfWakeType && !hasStartTime && !hasEndTime) {
|
|
75
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ No search criteria provided`);
|
|
76
|
+
throw new Error("至少需要提供一个检索条件:rangeType、alarmState、daysOfWakeType 或时间范围(startTime + endTime)");
|
|
77
|
+
}
|
|
78
|
+
// ===== Validate rangeType =====
|
|
79
|
+
if (hasRangeType) {
|
|
80
|
+
if (typeof params.rangeType !== "string") {
|
|
81
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid rangeType type`);
|
|
82
|
+
throw new Error("rangeType must be a string");
|
|
83
|
+
}
|
|
84
|
+
if (!RANGE_TYPE_VALUES.includes(params.rangeType)) {
|
|
85
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid rangeType value: ${params.rangeType}`);
|
|
86
|
+
throw new Error(`rangeType must be one of: ${RANGE_TYPE_VALUES.join(", ")}`);
|
|
87
|
+
}
|
|
88
|
+
logger.log(`[SEARCH_ALARM_TOOL] - rangeType: ${params.rangeType}`);
|
|
89
|
+
}
|
|
90
|
+
// ===== Validate alarmState =====
|
|
91
|
+
if (hasAlarmState) {
|
|
92
|
+
if (typeof params.alarmState !== "number") {
|
|
93
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid alarmState type`);
|
|
94
|
+
throw new Error("alarmState must be a number");
|
|
95
|
+
}
|
|
96
|
+
if (!ALARM_STATE_VALUES.includes(params.alarmState)) {
|
|
97
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid alarmState value: ${params.alarmState}`);
|
|
98
|
+
throw new Error(`alarmState must be one of: ${ALARM_STATE_VALUES.join(", ")}`);
|
|
99
|
+
}
|
|
100
|
+
logger.log(`[SEARCH_ALARM_TOOL] - alarmState: ${params.alarmState}`);
|
|
101
|
+
}
|
|
102
|
+
// ===== Validate daysOfWakeType =====
|
|
103
|
+
if (hasDaysOfWakeType) {
|
|
104
|
+
if (typeof params.daysOfWakeType !== "number") {
|
|
105
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid daysOfWakeType type`);
|
|
106
|
+
throw new Error("daysOfWakeType must be a number");
|
|
107
|
+
}
|
|
108
|
+
if (!DAYS_OF_WAKE_TYPE_VALUES.includes(params.daysOfWakeType)) {
|
|
109
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid daysOfWakeType value: ${params.daysOfWakeType}`);
|
|
110
|
+
throw new Error(`daysOfWakeType must be one of: ${DAYS_OF_WAKE_TYPE_VALUES.join(", ")}`);
|
|
111
|
+
}
|
|
112
|
+
logger.log(`[SEARCH_ALARM_TOOL] - daysOfWakeType: ${params.daysOfWakeType}`);
|
|
113
|
+
}
|
|
114
|
+
// ===== Validate time interval (startTime and endTime must be provided together) =====
|
|
115
|
+
if (hasStartTime !== hasEndTime) {
|
|
116
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ startTime and endTime must be provided together`);
|
|
117
|
+
throw new Error("startTime 和 endTime 必须一起提供");
|
|
118
|
+
}
|
|
119
|
+
let timeInterval = null;
|
|
120
|
+
if (hasStartTime && hasEndTime) {
|
|
121
|
+
// Parse and convert startTime and endTime to timestamps
|
|
122
|
+
logger.log(`[SEARCH_ALARM_TOOL] 🕒 Parsing time interval...`);
|
|
123
|
+
logger.log(`[SEARCH_ALARM_TOOL] - startTime input: ${params.startTime}`);
|
|
124
|
+
logger.log(`[SEARCH_ALARM_TOOL] - endTime input: ${params.endTime}`);
|
|
125
|
+
const startTimeMs = parseAlarmTimeToTimestamp(params.startTime);
|
|
126
|
+
const endTimeMs = parseAlarmTimeToTimestamp(params.endTime);
|
|
127
|
+
if (startTimeMs === null) {
|
|
128
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid startTime format`);
|
|
129
|
+
throw new Error("Invalid startTime format. Required format: YYYYMMDD hhmmss (e.g., 20240315 000000)");
|
|
130
|
+
}
|
|
131
|
+
if (endTimeMs === null) {
|
|
132
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid endTime format`);
|
|
133
|
+
throw new Error("Invalid endTime format. Required format: YYYYMMDD hhmmss (e.g., 20240315 235959)");
|
|
134
|
+
}
|
|
135
|
+
if (startTimeMs >= endTimeMs) {
|
|
136
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ startTime must be before endTime`);
|
|
137
|
+
throw new Error("startTime 必须早于 endTime");
|
|
138
|
+
}
|
|
139
|
+
timeInterval = [startTimeMs, endTimeMs];
|
|
140
|
+
logger.log(`[SEARCH_ALARM_TOOL] ✅ Time interval parsed: [${startTimeMs}, ${endTimeMs}]`);
|
|
141
|
+
}
|
|
142
|
+
// Get session context
|
|
143
|
+
logger.log(`[SEARCH_ALARM_TOOL] 🔍 Attempting to get session context...`);
|
|
144
|
+
const sessionContext = getCurrentSessionContext();
|
|
145
|
+
if (!sessionContext) {
|
|
146
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ FAILED: No active session found!`);
|
|
147
|
+
logger.error(`[SEARCH_ALARM_TOOL] - toolCallId: ${toolCallId}`);
|
|
148
|
+
throw new Error("No active XY session found. Search alarm tool can only be used during an active conversation.");
|
|
149
|
+
}
|
|
150
|
+
logger.log(`[SEARCH_ALARM_TOOL] ✅ Session context found`);
|
|
151
|
+
logger.log(`[SEARCH_ALARM_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
152
|
+
logger.log(`[SEARCH_ALARM_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
153
|
+
logger.log(`[SEARCH_ALARM_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
154
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
155
|
+
// Get WebSocket manager
|
|
156
|
+
logger.log(`[SEARCH_ALARM_TOOL] 🔌 Getting WebSocket manager...`);
|
|
157
|
+
const wsManager = getXYWebSocketManager(config);
|
|
158
|
+
logger.log(`[SEARCH_ALARM_TOOL] ✅ WebSocket manager obtained`);
|
|
159
|
+
// Build SearchAlarm command
|
|
160
|
+
logger.log(`[SEARCH_ALARM_TOOL] 📦 Building SearchAlarm command...`);
|
|
161
|
+
// Build intentParam with provided search criteria
|
|
162
|
+
const intentParam = {};
|
|
163
|
+
if (hasRangeType) {
|
|
164
|
+
intentParam.rangeType = params.rangeType;
|
|
165
|
+
}
|
|
166
|
+
if (hasAlarmState) {
|
|
167
|
+
intentParam.alarmState = params.alarmState;
|
|
168
|
+
}
|
|
169
|
+
if (hasDaysOfWakeType) {
|
|
170
|
+
intentParam.daysOfWakeType = params.daysOfWakeType;
|
|
171
|
+
}
|
|
172
|
+
if (timeInterval) {
|
|
173
|
+
intentParam.timeInterval = timeInterval;
|
|
174
|
+
}
|
|
175
|
+
const command = {
|
|
176
|
+
header: {
|
|
177
|
+
namespace: "Common",
|
|
178
|
+
name: "Action",
|
|
179
|
+
},
|
|
180
|
+
payload: {
|
|
181
|
+
cardParam: {},
|
|
182
|
+
executeParam: {
|
|
183
|
+
executeMode: "background",
|
|
184
|
+
intentName: "SearchAlarm",
|
|
185
|
+
bundleName: "com.huawei.hmos.clock",
|
|
186
|
+
needUnlock: true,
|
|
187
|
+
actionResponse: true,
|
|
188
|
+
appType: "OHOS_APP",
|
|
189
|
+
timeOut: 5,
|
|
190
|
+
intentParam,
|
|
191
|
+
permissionId: [],
|
|
192
|
+
achieveType: "INTENT",
|
|
193
|
+
},
|
|
194
|
+
responses: [
|
|
195
|
+
{
|
|
196
|
+
resultCode: "",
|
|
197
|
+
displayText: "",
|
|
198
|
+
ttsText: "",
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
needUploadResult: true,
|
|
202
|
+
noHalfPage: false,
|
|
203
|
+
pageControlRelated: false,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
// Send command and wait for response (60 second timeout)
|
|
207
|
+
logger.log(`[SEARCH_ALARM_TOOL] ⏳ Setting up promise to wait for alarm search response...`);
|
|
208
|
+
logger.log(`[SEARCH_ALARM_TOOL] - Timeout: 60 seconds`);
|
|
209
|
+
return new Promise((resolve, reject) => {
|
|
210
|
+
const timeout = setTimeout(() => {
|
|
211
|
+
logger.error(`[SEARCH_ALARM_TOOL] ⏰ Timeout: No response received within 60 seconds`);
|
|
212
|
+
wsManager.off("data-event", handler);
|
|
213
|
+
reject(new Error("检索闹钟超时(60秒)"));
|
|
214
|
+
}, 60000);
|
|
215
|
+
// Listen for data events from WebSocket
|
|
216
|
+
const handler = (event) => {
|
|
217
|
+
logger.log(`[SEARCH_ALARM_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
218
|
+
if (event.intentName === "SearchAlarm") {
|
|
219
|
+
logger.log(`[SEARCH_ALARM_TOOL] 🎯 SearchAlarm event received`);
|
|
220
|
+
logger.log(`[SEARCH_ALARM_TOOL] - status: ${event.status}`);
|
|
221
|
+
clearTimeout(timeout);
|
|
222
|
+
wsManager.off("data-event", handler);
|
|
223
|
+
if (event.status === "success" && event.outputs) {
|
|
224
|
+
logger.log(`[SEARCH_ALARM_TOOL] ✅ Alarm search completed successfully`);
|
|
225
|
+
logger.log(`[SEARCH_ALARM_TOOL] - outputs:`, JSON.stringify(event.outputs));
|
|
226
|
+
// Check for error code in outputs
|
|
227
|
+
const code = event.outputs.code !== undefined ? event.outputs.code : null;
|
|
228
|
+
if (code !== null && code !== 0) {
|
|
229
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Device returned error`);
|
|
230
|
+
logger.error(`[SEARCH_ALARM_TOOL] - code: ${code}`);
|
|
231
|
+
const errorMsg = event.outputs.errorMsg || event.outputs.errMsg || "未知错误";
|
|
232
|
+
logger.error(`[SEARCH_ALARM_TOOL] - errorMsg: ${errorMsg}`);
|
|
233
|
+
reject(new Error(`检索闹钟失败: ${errorMsg} (错误代码: ${code})`));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// Extract result.items with safe checks
|
|
237
|
+
const result = event.outputs.result;
|
|
238
|
+
let items = [];
|
|
239
|
+
if (result && typeof result === "object" && Array.isArray(result.items)) {
|
|
240
|
+
items = result.items;
|
|
241
|
+
logger.log(`[SEARCH_ALARM_TOOL] 📋 Found ${items.length} alarm(s)`);
|
|
242
|
+
// Parse JSON strings in items array
|
|
243
|
+
// Items are returned as JSON strings that need to be parsed
|
|
244
|
+
const parsedItems = items.map((itemStr, index) => {
|
|
245
|
+
if (typeof itemStr !== "string") {
|
|
246
|
+
logger.warn(`[SEARCH_ALARM_TOOL] ⚠️ Item at index ${index} is not a string:`, typeof itemStr);
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const parsed = JSON.parse(itemStr);
|
|
251
|
+
logger.log(`[SEARCH_ALARM_TOOL] 📋 Parsed alarm [${index}]:`, JSON.stringify(parsed));
|
|
252
|
+
return parsed;
|
|
253
|
+
}
|
|
254
|
+
catch (parseError) {
|
|
255
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Failed to parse item at index ${index}:`, parseError);
|
|
256
|
+
logger.error(`[SEARCH_ALARM_TOOL] - itemStr: ${itemStr}`);
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}).filter((item) => item !== null);
|
|
260
|
+
logger.log(`[SEARCH_ALARM_TOOL] 🎉 Successfully parsed ${parsedItems.length} alarm(s)`);
|
|
261
|
+
resolve({
|
|
262
|
+
content: [
|
|
263
|
+
{
|
|
264
|
+
type: "text",
|
|
265
|
+
text: JSON.stringify(parsedItems),
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
logger.warn(`[SEARCH_ALARM_TOOL] ⚠️ No items found in result or result is invalid`);
|
|
272
|
+
logger.warn(`[SEARCH_ALARM_TOOL] - result:`, JSON.stringify(result || {}));
|
|
273
|
+
// Return empty array
|
|
274
|
+
resolve({
|
|
275
|
+
content: [
|
|
276
|
+
{
|
|
277
|
+
type: "text",
|
|
278
|
+
text: "[]",
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Alarm search failed`);
|
|
286
|
+
logger.error(`[SEARCH_ALARM_TOOL] - status: ${event.status}`);
|
|
287
|
+
logger.error(`[SEARCH_ALARM_TOOL] - outputs:`, JSON.stringify(event.outputs || {}));
|
|
288
|
+
reject(new Error(`检索闹钟失败: ${event.status}`));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
// Register event handler
|
|
293
|
+
logger.log(`[SEARCH_ALARM_TOOL] 📡 Registering data-event handler on WebSocket manager`);
|
|
294
|
+
wsManager.on("data-event", handler);
|
|
295
|
+
// Send the command
|
|
296
|
+
logger.log(`[SEARCH_ALARM_TOOL] 📤 Sending SearchAlarm command...`);
|
|
297
|
+
sendCommand({
|
|
298
|
+
config,
|
|
299
|
+
sessionId,
|
|
300
|
+
taskId,
|
|
301
|
+
messageId,
|
|
302
|
+
command,
|
|
303
|
+
})
|
|
304
|
+
.then(() => {
|
|
305
|
+
logger.log(`[SEARCH_ALARM_TOOL] ✅ Command sent successfully, waiting for response...`);
|
|
306
|
+
})
|
|
307
|
+
.catch((error) => {
|
|
308
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Failed to send command:`, error);
|
|
309
|
+
clearTimeout(timeout);
|
|
310
|
+
wsManager.off("data-event", handler);
|
|
311
|
+
reject(error);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* Parse alarmTime string (YYYYMMDD hhmmss) to timestamp in milliseconds
|
|
318
|
+
* @param alarmTime - Time string in format "YYYYMMDD hhmmss" (e.g., "20240315 143000")
|
|
319
|
+
* @returns Timestamp in milliseconds, or null if parsing fails
|
|
320
|
+
*/
|
|
321
|
+
function parseAlarmTimeToTimestamp(alarmTime) {
|
|
322
|
+
try {
|
|
323
|
+
// Expected format: YYYYMMDD hhmmss
|
|
324
|
+
// Example: 20240315 143000
|
|
325
|
+
const trimmed = alarmTime.trim();
|
|
326
|
+
// Check basic format (should have at least 13 characters: YYYYMMDD hhmmss)
|
|
327
|
+
if (trimmed.length < 13) {
|
|
328
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ alarmTime too short: ${trimmed}`);
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
// Extract date and time parts
|
|
332
|
+
// Format: YYYYMMDD hhmmss
|
|
333
|
+
const datePart = trimmed.substring(0, 8); // YYYYMMDD
|
|
334
|
+
const timePart = trimmed.substring(8).trim(); // hhmmss (may have leading space)
|
|
335
|
+
logger.log(`[SEARCH_ALARM_TOOL] - datePart: ${datePart}, timePart: ${timePart}`);
|
|
336
|
+
// Validate lengths
|
|
337
|
+
if (datePart.length !== 8 || timePart.length !== 6) {
|
|
338
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid part lengths: datePart=${datePart.length}, timePart=${timePart.length}`);
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
// Parse components
|
|
342
|
+
const year = parseInt(datePart.substring(0, 4), 10);
|
|
343
|
+
const month = parseInt(datePart.substring(4, 6), 10);
|
|
344
|
+
const day = parseInt(datePart.substring(6, 8), 10);
|
|
345
|
+
const hour = parseInt(timePart.substring(0, 2), 10);
|
|
346
|
+
const minute = parseInt(timePart.substring(2, 4), 10);
|
|
347
|
+
const second = parseInt(timePart.substring(4, 6), 10);
|
|
348
|
+
logger.log(`[SEARCH_ALARM_TOOL] - Parsed: ${year}-${month}-${day} ${hour}:${minute}:${second}`);
|
|
349
|
+
// Validate values
|
|
350
|
+
if (isNaN(year) || isNaN(month) || isNaN(day) ||
|
|
351
|
+
isNaN(hour) || isNaN(minute) || isNaN(second)) {
|
|
352
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ NaN detected in parsed values`);
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
// Validate ranges
|
|
356
|
+
if (month < 1 || month > 12) {
|
|
357
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid month: ${month}`);
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
if (day < 1 || day > 31) {
|
|
361
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid day: ${day}`);
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
if (hour < 0 || hour > 23) {
|
|
365
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid hour: ${hour}`);
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
if (minute < 0 || minute > 59) {
|
|
369
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid minute: ${minute}`);
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
if (second < 0 || second > 59) {
|
|
373
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Invalid second: ${second}`);
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
// Create Date object and get timestamp
|
|
377
|
+
const date = new Date(year, month - 1, day, hour, minute, second);
|
|
378
|
+
const timestamp = date.getTime();
|
|
379
|
+
if (isNaN(timestamp)) {
|
|
380
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Generated timestamp is NaN`);
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
return timestamp;
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
logger.error(`[SEARCH_ALARM_TOOL] ❌ Exception in parseAlarmTimeToTimestamp:`, error);
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
@@ -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 calendar event tool - searches calendar events on user's device.
|
|
@@ -110,7 +110,7 @@ export const searchCalendarTool = {
|
|
|
110
110
|
logger.log(`[SEARCH_CALENDAR_TOOL] - endTime timestamp: ${endTimeMs}`);
|
|
111
111
|
// Get session context
|
|
112
112
|
logger.log(`[SEARCH_CALENDAR_TOOL] 🔍 Attempting to get session context...`);
|
|
113
|
-
const sessionContext =
|
|
113
|
+
const sessionContext = getCurrentSessionContext();
|
|
114
114
|
if (!sessionContext) {
|
|
115
115
|
logger.error(`[SEARCH_CALENDAR_TOOL] ❌ FAILED: No active session found!`);
|
|
116
116
|
logger.error(`[SEARCH_CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
|
|
@@ -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 contact tool - searches contacts on user's device.
|
|
@@ -32,7 +32,7 @@ export const searchContactTool = {
|
|
|
32
32
|
}
|
|
33
33
|
// Get session context
|
|
34
34
|
logger.log(`[SEARCH_CONTACT_TOOL] 🔍 Attempting to get session context...`);
|
|
35
|
-
const sessionContext =
|
|
35
|
+
const sessionContext = getCurrentSessionContext();
|
|
36
36
|
if (!sessionContext) {
|
|
37
37
|
logger.error(`[SEARCH_CONTACT_TOOL] ❌ FAILED: No active session found!`);
|
|
38
38
|
logger.error(`[SEARCH_CONTACT_TOOL] - toolCallId: ${toolCallId}`);
|
|
@@ -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 file tool - searches files on user's device file system.
|
|
7
|
+
* Returns matching files based on keyword search in file name or content.
|
|
8
|
+
*/
|
|
9
|
+
export const searchFileTool = {
|
|
10
|
+
name: "search_file",
|
|
11
|
+
label: "Search File",
|
|
12
|
+
description: "搜索手机文件系统的文件。根据关键词搜索文件名称或内容,返回匹配的文件列表(包括文件名、路径、大小、修改时间等信息)。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
|
|
13
|
+
parameters: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
query: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "搜索关键词,用于匹配文件名称、后缀名或文件内容",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
required: ["query"],
|
|
22
|
+
},
|
|
23
|
+
async execute(toolCallId, params) {
|
|
24
|
+
logger.log(`[SEARCH_FILE_TOOL] 🚀 Starting execution`);
|
|
25
|
+
logger.log(`[SEARCH_FILE_TOOL] - toolCallId: ${toolCallId}`);
|
|
26
|
+
logger.log(`[SEARCH_FILE_TOOL] - params:`, JSON.stringify(params));
|
|
27
|
+
logger.log(`[SEARCH_FILE_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
28
|
+
// Validate query parameter
|
|
29
|
+
if (!params.query || typeof params.query !== "string" || params.query.trim() === "") {
|
|
30
|
+
logger.error(`[SEARCH_FILE_TOOL] ❌ Missing or invalid query parameter`);
|
|
31
|
+
throw new Error("Missing required parameter: query must be a non-empty string");
|
|
32
|
+
}
|
|
33
|
+
logger.log(`[SEARCH_FILE_TOOL] 🔍 Searching for files with keyword: ${params.query}`);
|
|
34
|
+
// Get session context
|
|
35
|
+
logger.log(`[SEARCH_FILE_TOOL] 🔍 Attempting to get session context...`);
|
|
36
|
+
const sessionContext = getCurrentSessionContext();
|
|
37
|
+
if (!sessionContext) {
|
|
38
|
+
logger.error(`[SEARCH_FILE_TOOL] ❌ FAILED: No active session found!`);
|
|
39
|
+
logger.error(`[SEARCH_FILE_TOOL] - toolCallId: ${toolCallId}`);
|
|
40
|
+
throw new Error("No active XY session found. Search file tool can only be used during an active conversation.");
|
|
41
|
+
}
|
|
42
|
+
logger.log(`[SEARCH_FILE_TOOL] ✅ Session context found`);
|
|
43
|
+
logger.log(`[SEARCH_FILE_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
44
|
+
logger.log(`[SEARCH_FILE_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
45
|
+
logger.log(`[SEARCH_FILE_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
46
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
47
|
+
// Get WebSocket manager
|
|
48
|
+
logger.log(`[SEARCH_FILE_TOOL] 🔌 Getting WebSocket manager...`);
|
|
49
|
+
const wsManager = getXYWebSocketManager(config);
|
|
50
|
+
logger.log(`[SEARCH_FILE_TOOL] ✅ WebSocket manager obtained`);
|
|
51
|
+
// Build SearchFile command
|
|
52
|
+
logger.log(`[SEARCH_FILE_TOOL] 📦 Building SearchFile command...`);
|
|
53
|
+
const command = {
|
|
54
|
+
header: {
|
|
55
|
+
namespace: "Common",
|
|
56
|
+
name: "Action",
|
|
57
|
+
},
|
|
58
|
+
payload: {
|
|
59
|
+
cardParam: {},
|
|
60
|
+
executeParam: {
|
|
61
|
+
executeMode: "background",
|
|
62
|
+
intentName: "SearchFile",
|
|
63
|
+
bundleName: "com.huawei.hmos.aidispatchservice",
|
|
64
|
+
needUnlock: true,
|
|
65
|
+
actionResponse: true,
|
|
66
|
+
appType: "OHOS_APP",
|
|
67
|
+
timeOut: 5,
|
|
68
|
+
intentParam: {
|
|
69
|
+
query: params.query.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_FILE_TOOL] 📋 Command details:`, JSON.stringify(command, null, 2));
|
|
87
|
+
// Send command and wait for response (60 second timeout)
|
|
88
|
+
logger.log(`[SEARCH_FILE_TOOL] ⏳ Setting up promise to wait for file search response...`);
|
|
89
|
+
logger.log(`[SEARCH_FILE_TOOL] - Timeout: 60 seconds`);
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const timeout = setTimeout(() => {
|
|
92
|
+
logger.error(`[SEARCH_FILE_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_FILE_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
99
|
+
if (event.intentName === "SearchFile") {
|
|
100
|
+
logger.log(`[SEARCH_FILE_TOOL] 🎯 SearchFile event received`);
|
|
101
|
+
logger.log(`[SEARCH_FILE_TOOL] - status: ${event.status}`);
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
wsManager.off("data-event", handler);
|
|
104
|
+
if (event.status === "success" && event.outputs) {
|
|
105
|
+
logger.log(`[SEARCH_FILE_TOOL] ✅ File search response received`);
|
|
106
|
+
logger.log(`[SEARCH_FILE_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_FILE_TOOL] ❌ Device returned error`);
|
|
111
|
+
logger.error(`[SEARCH_FILE_TOOL] - code: ${code}`);
|
|
112
|
+
const errorMsg = event.outputs.errorMsg || event.outputs.errMsg || "未知错误";
|
|
113
|
+
logger.error(`[SEARCH_FILE_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_FILE_TOOL] 📋 Found ${items.length} file(s)`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
logger.warn(`[SEARCH_FILE_TOOL] ⚠️ No items found in result or result is invalid`);
|
|
126
|
+
logger.warn(`[SEARCH_FILE_TOOL] - result:`, JSON.stringify(result || {}));
|
|
127
|
+
}
|
|
128
|
+
// Return items array as JSON string
|
|
129
|
+
logger.log(`[SEARCH_FILE_TOOL] 🎉 File search completed successfully`);
|
|
130
|
+
logger.log(`[SEARCH_FILE_TOOL] - keyword: ${params.query}`);
|
|
131
|
+
logger.log(`[SEARCH_FILE_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_FILE_TOOL] ❌ File search failed`);
|
|
143
|
+
logger.error(`[SEARCH_FILE_TOOL] - status: ${event.status}`);
|
|
144
|
+
logger.error(`[SEARCH_FILE_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_FILE_TOOL] 📡 Registering data-event handler on WebSocket manager`);
|
|
152
|
+
wsManager.on("data-event", handler);
|
|
153
|
+
// Send the command
|
|
154
|
+
logger.log(`[SEARCH_FILE_TOOL] 📤 Sending SearchFile command...`);
|
|
155
|
+
sendCommand({
|
|
156
|
+
config,
|
|
157
|
+
sessionId,
|
|
158
|
+
taskId,
|
|
159
|
+
messageId,
|
|
160
|
+
command,
|
|
161
|
+
})
|
|
162
|
+
.then(() => {
|
|
163
|
+
logger.log(`[SEARCH_FILE_TOOL] ✅ Command sent successfully, waiting for response...`);
|
|
164
|
+
})
|
|
165
|
+
.catch((error) => {
|
|
166
|
+
logger.error(`[SEARCH_FILE_TOOL] ❌ Failed to send command:`, error);
|
|
167
|
+
clearTimeout(timeout);
|
|
168
|
+
wsManager.off("data-event", handler);
|
|
169
|
+
reject(error);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
};
|