@ynhcj/xiaoyi-channel 0.0.26-beta → 0.0.26-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 +48 -2
- package/dist/src/bot.js +81 -28
- package/dist/src/channel.js +33 -7
- package/dist/src/client.js +11 -27
- package/dist/src/config.js +2 -2
- package/dist/src/cspl/call-api.d.ts +3 -0
- package/dist/src/cspl/call-api.js +107 -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 +92 -0
- package/dist/src/formatter.d.ts +14 -0
- package/dist/src/formatter.js +49 -25
- package/dist/src/heartbeat.js +0 -4
- package/dist/src/monitor.js +12 -0
- 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 +52 -6
- package/dist/src/parser.d.ts +7 -0
- package/dist/src/parser.js +22 -0
- package/dist/src/push.d.ts +8 -1
- package/dist/src/push.js +30 -22
- package/dist/src/reply-dispatcher.js +39 -5
- 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 +7 -40
- package/dist/src/tools/call-phone-tool.d.ts +5 -0
- package/dist/src/tools/call-phone-tool.js +142 -0
- package/dist/src/tools/create-alarm-tool.d.ts +7 -0
- package/dist/src/tools/create-alarm-tool.js +349 -0
- package/dist/src/tools/delete-alarm-tool.d.ts +11 -0
- package/dist/src/tools/delete-alarm-tool.js +174 -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 +8 -42
- package/dist/src/tools/modify-alarm-tool.d.ts +9 -0
- package/dist/src/tools/modify-alarm-tool.js +365 -0
- package/dist/src/tools/modify-note-tool.js +5 -43
- package/dist/src/tools/note-tool.js +33 -24
- package/dist/src/tools/search-alarm-tool.d.ts +8 -0
- package/dist/src/tools/search-alarm-tool.js +285 -0
- package/dist/src/tools/search-calendar-tool.js +10 -84
- package/dist/src/tools/search-contact-tool.js +4 -57
- package/dist/src/tools/search-file-tool.d.ts +5 -0
- package/dist/src/tools/search-file-tool.js +128 -0
- package/dist/src/tools/search-message-tool.d.ts +5 -0
- package/dist/src/tools/search-message-tool.js +116 -0
- package/dist/src/tools/search-note-tool.js +6 -24
- package/dist/src/tools/search-photo-gallery-tool.js +40 -61
- package/dist/src/tools/send-file-to-user-tool.d.ts +5 -0
- package/dist/src/tools/send-file-to-user-tool.js +279 -0
- package/dist/src/tools/send-message-tool.d.ts +5 -0
- package/dist/src/tools/send-message-tool.js +138 -0
- package/dist/src/tools/session-manager.d.ts +15 -0
- package/dist/src/tools/session-manager.js +43 -31
- package/dist/src/tools/upload-file-tool.d.ts +13 -0
- package/dist/src/tools/upload-file-tool.js +216 -0
- package/dist/src/tools/upload-photo-tool.js +2 -44
- package/dist/src/tools/view-push-result-tool.d.ts +5 -0
- package/dist/src/tools/view-push-result-tool.js +107 -0
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +5 -0
- package/dist/src/tools/xiaoyi-collection-tool.js +112 -0
- package/dist/src/tools/xiaoyi-gui-tool.js +2 -36
- package/dist/src/trigger-handler.d.ts +22 -0
- package/dist/src/trigger-handler.js +59 -0
- package/dist/src/types.d.ts +1 -8
- package/dist/src/types.js +4 -0
- package/dist/src/utils/pushdata-manager.d.ts +28 -0
- package/dist/src/utils/pushdata-manager.js +171 -0
- package/dist/src/utils/pushid-manager.d.ts +12 -0
- package/dist/src/utils/pushid-manager.js +105 -0
- package/dist/src/websocket.d.ts +25 -31
- package/dist/src/websocket.js +233 -271
- package/package.json +2 -2
- 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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
|
+
// Enum definitions for alarm search parameters
|
|
5
|
+
const RANGE_TYPE_VALUES = ["all", "next", "current"];
|
|
6
|
+
const ALARM_STATE_VALUES = [0, 1];
|
|
7
|
+
const DAYS_OF_WAKE_TYPE_VALUES = [0, 1, 2, 3, 4];
|
|
8
|
+
/**
|
|
9
|
+
* XY search alarm tool - searches alarms on user's device.
|
|
10
|
+
* Returns matching alarms based on various filter criteria.
|
|
11
|
+
*
|
|
12
|
+
* At least one search criterion must be provided.
|
|
13
|
+
* Multiple criteria can be combined.
|
|
14
|
+
*/
|
|
15
|
+
export const searchAlarmTool = {
|
|
16
|
+
name: "search_alarm",
|
|
17
|
+
label: "Search Alarm",
|
|
18
|
+
description: `检索用户设备上的闹钟。支持多种检索条件,至少需要提供一个检索条件,多个条件可以组合使用。
|
|
19
|
+
|
|
20
|
+
检索条件(至少提供一个):
|
|
21
|
+
- rangeType: 查询范围,枚举值:all=查询所有闹钟,next=查找下一个响铃闹钟,current=一小时内最近一次增查改的闹钟
|
|
22
|
+
- alarmState: 闹钟开启状态,0=关闭,1=开启
|
|
23
|
+
- daysOfWakeType: 闹钟响铃类型,0=单次响铃,1=法定节假日,2=每天,3=自定义时间,4=法定工作日
|
|
24
|
+
- startTime: 时间间隔开始,格式 YYYYMMDD hhmmss(例如:20240315 000000),需要与 endTime 一起使用
|
|
25
|
+
- endTime: 时间间隔结束,格式 YYYYMMDD hhmmss(例如:20240315 235959),需要与 startTime 一起使用
|
|
26
|
+
|
|
27
|
+
使用示例:
|
|
28
|
+
- 查询所有闹钟:{"rangeType": "all"}
|
|
29
|
+
- 查询已开启的闹钟:{"alarmState": 1}
|
|
30
|
+
- 查询每天响铃的闹钟:{"daysOfWakeType": 2}
|
|
31
|
+
- 查询某个时间段的闹钟:{"startTime": "20240315 000000", "endTime": "20240315 235959"}
|
|
32
|
+
|
|
33
|
+
注意:
|
|
34
|
+
a. 操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。
|
|
35
|
+
b. 使用该工具之前需获取当前真实时间
|
|
36
|
+
|
|
37
|
+
回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。`,
|
|
38
|
+
parameters: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
rangeType: {
|
|
42
|
+
type: "string",
|
|
43
|
+
enum: ["all", "next", "current"],
|
|
44
|
+
description: "查询范围:all=所有闹钟,next=下一个响铃闹钟,current=一小时内最近修改的闹钟",
|
|
45
|
+
},
|
|
46
|
+
alarmState: {
|
|
47
|
+
type: "number",
|
|
48
|
+
enum: [0, 1],
|
|
49
|
+
description: "闹钟开启状态:0=关闭,1=开启",
|
|
50
|
+
},
|
|
51
|
+
daysOfWakeType: {
|
|
52
|
+
type: "number",
|
|
53
|
+
enum: [0, 1, 2, 3, 4],
|
|
54
|
+
description: "闹钟响铃类型:0=单次,1=法定节假日,2=每天,3=自定义,4=法定工作日",
|
|
55
|
+
},
|
|
56
|
+
startTime: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "时间间隔开始,格式 YYYYMMDD hhmmss(例如:20240315 000000),必须与 endTime 一起使用",
|
|
59
|
+
},
|
|
60
|
+
endTime: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "时间间隔结束,格式 YYYYMMDD hhmmss(例如:20240315 235959),必须与 startTime 一起使用",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
async execute(toolCallId, params) {
|
|
67
|
+
// ===== Validate at least one search criterion is provided =====
|
|
68
|
+
const hasRangeType = params.rangeType !== undefined && params.rangeType !== null;
|
|
69
|
+
const hasAlarmState = params.alarmState !== undefined && params.alarmState !== null;
|
|
70
|
+
const hasDaysOfWakeType = params.daysOfWakeType !== undefined && params.daysOfWakeType !== null;
|
|
71
|
+
const hasStartTime = params.startTime !== undefined && params.startTime !== null;
|
|
72
|
+
const hasEndTime = params.endTime !== undefined && params.endTime !== null;
|
|
73
|
+
if (!hasRangeType && !hasAlarmState && !hasDaysOfWakeType && !hasStartTime && !hasEndTime) {
|
|
74
|
+
throw new Error("至少需要提供一个检索条件:rangeType、alarmState、daysOfWakeType 或时间范围(startTime + endTime)");
|
|
75
|
+
}
|
|
76
|
+
// ===== Validate rangeType =====
|
|
77
|
+
if (hasRangeType) {
|
|
78
|
+
if (typeof params.rangeType !== "string") {
|
|
79
|
+
throw new Error("rangeType must be a string");
|
|
80
|
+
}
|
|
81
|
+
if (!RANGE_TYPE_VALUES.includes(params.rangeType)) {
|
|
82
|
+
throw new Error(`rangeType must be one of: ${RANGE_TYPE_VALUES.join(", ")}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ===== Validate alarmState =====
|
|
86
|
+
if (hasAlarmState) {
|
|
87
|
+
if (typeof params.alarmState !== "number") {
|
|
88
|
+
throw new Error("alarmState must be a number");
|
|
89
|
+
}
|
|
90
|
+
if (!ALARM_STATE_VALUES.includes(params.alarmState)) {
|
|
91
|
+
throw new Error(`alarmState must be one of: ${ALARM_STATE_VALUES.join(", ")}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ===== Validate daysOfWakeType =====
|
|
95
|
+
if (hasDaysOfWakeType) {
|
|
96
|
+
if (typeof params.daysOfWakeType !== "number") {
|
|
97
|
+
throw new Error("daysOfWakeType must be a number");
|
|
98
|
+
}
|
|
99
|
+
if (!DAYS_OF_WAKE_TYPE_VALUES.includes(params.daysOfWakeType)) {
|
|
100
|
+
throw new Error(`daysOfWakeType must be one of: ${DAYS_OF_WAKE_TYPE_VALUES.join(", ")}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// ===== Validate time interval (startTime and endTime must be provided together) =====
|
|
104
|
+
if (hasStartTime !== hasEndTime) {
|
|
105
|
+
throw new Error("startTime 和 endTime 必须一起提供");
|
|
106
|
+
}
|
|
107
|
+
let timeInterval = null;
|
|
108
|
+
if (hasStartTime && hasEndTime) {
|
|
109
|
+
// Parse and convert startTime and endTime to timestamps
|
|
110
|
+
const startTimeMs = parseAlarmTimeToTimestamp(params.startTime);
|
|
111
|
+
const endTimeMs = parseAlarmTimeToTimestamp(params.endTime);
|
|
112
|
+
if (startTimeMs === null) {
|
|
113
|
+
throw new Error("Invalid startTime format. Required format: YYYYMMDD hhmmss (e.g., 20240315 000000)");
|
|
114
|
+
}
|
|
115
|
+
if (endTimeMs === null) {
|
|
116
|
+
throw new Error("Invalid endTime format. Required format: YYYYMMDD hhmmss (e.g., 20240315 235959)");
|
|
117
|
+
}
|
|
118
|
+
if (startTimeMs >= endTimeMs) {
|
|
119
|
+
throw new Error("startTime 必须早于 endTime");
|
|
120
|
+
}
|
|
121
|
+
timeInterval = [startTimeMs, endTimeMs];
|
|
122
|
+
}
|
|
123
|
+
// Get session context
|
|
124
|
+
const sessionContext = getCurrentSessionContext();
|
|
125
|
+
if (!sessionContext) {
|
|
126
|
+
throw new Error("No active XY session found. Search alarm tool can only be used during an active conversation.");
|
|
127
|
+
}
|
|
128
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
129
|
+
// Get WebSocket manager
|
|
130
|
+
const wsManager = getXYWebSocketManager(config);
|
|
131
|
+
// Build SearchAlarm command
|
|
132
|
+
// Build intentParam with provided search criteria
|
|
133
|
+
const intentParam = {};
|
|
134
|
+
if (hasRangeType) {
|
|
135
|
+
intentParam.rangeType = params.rangeType;
|
|
136
|
+
}
|
|
137
|
+
if (hasAlarmState) {
|
|
138
|
+
intentParam.alarmState = params.alarmState;
|
|
139
|
+
}
|
|
140
|
+
if (hasDaysOfWakeType) {
|
|
141
|
+
intentParam.daysOfWakeType = params.daysOfWakeType;
|
|
142
|
+
}
|
|
143
|
+
if (timeInterval) {
|
|
144
|
+
intentParam.timeInterval = timeInterval;
|
|
145
|
+
}
|
|
146
|
+
const command = {
|
|
147
|
+
header: {
|
|
148
|
+
namespace: "Common",
|
|
149
|
+
name: "Action",
|
|
150
|
+
},
|
|
151
|
+
payload: {
|
|
152
|
+
cardParam: {},
|
|
153
|
+
executeParam: {
|
|
154
|
+
executeMode: "background",
|
|
155
|
+
intentName: "SearchAlarm",
|
|
156
|
+
bundleName: "com.huawei.hmos.clock",
|
|
157
|
+
needUnlock: true,
|
|
158
|
+
actionResponse: true,
|
|
159
|
+
appType: "OHOS_APP",
|
|
160
|
+
timeOut: 5,
|
|
161
|
+
intentParam,
|
|
162
|
+
permissionId: [],
|
|
163
|
+
achieveType: "INTENT",
|
|
164
|
+
},
|
|
165
|
+
responses: [
|
|
166
|
+
{
|
|
167
|
+
resultCode: "",
|
|
168
|
+
displayText: "",
|
|
169
|
+
ttsText: "",
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
needUploadResult: true,
|
|
173
|
+
noHalfPage: false,
|
|
174
|
+
pageControlRelated: false,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
// Send command and wait for response (60 second timeout)
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
const timeout = setTimeout(() => {
|
|
180
|
+
wsManager.off("data-event", handler);
|
|
181
|
+
reject(new Error("检索闹钟超时(60秒)"));
|
|
182
|
+
}, 60000);
|
|
183
|
+
// Listen for data events from WebSocket
|
|
184
|
+
const handler = (event) => {
|
|
185
|
+
if (event.intentName === "SearchAlarm") {
|
|
186
|
+
clearTimeout(timeout);
|
|
187
|
+
wsManager.off("data-event", handler);
|
|
188
|
+
if (event.status === "success" && event.outputs) {
|
|
189
|
+
// 成功,直接返回完整的 event.outputs JSON 字符串
|
|
190
|
+
resolve({
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: "text",
|
|
194
|
+
text: JSON.stringify(event.outputs),
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
reject(new Error(`检索闹钟失败: ${event.status}`));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
// Register event handler
|
|
205
|
+
wsManager.on("data-event", handler);
|
|
206
|
+
// Send the command
|
|
207
|
+
sendCommand({
|
|
208
|
+
config,
|
|
209
|
+
sessionId,
|
|
210
|
+
taskId,
|
|
211
|
+
messageId,
|
|
212
|
+
command,
|
|
213
|
+
})
|
|
214
|
+
.then(() => {
|
|
215
|
+
})
|
|
216
|
+
.catch((error) => {
|
|
217
|
+
clearTimeout(timeout);
|
|
218
|
+
wsManager.off("data-event", handler);
|
|
219
|
+
reject(error);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Parse alarmTime string (YYYYMMDD hhmmss) to timestamp in milliseconds
|
|
226
|
+
* @param alarmTime - Time string in format "YYYYMMDD hhmmss" (e.g., "20240315 143000")
|
|
227
|
+
* @returns Timestamp in milliseconds, or null if parsing fails
|
|
228
|
+
*/
|
|
229
|
+
function parseAlarmTimeToTimestamp(alarmTime) {
|
|
230
|
+
try {
|
|
231
|
+
// Expected format: YYYYMMDD hhmmss
|
|
232
|
+
// Example: 20240315 143000
|
|
233
|
+
const trimmed = alarmTime.trim();
|
|
234
|
+
// Check basic format (should have at least 13 characters: YYYYMMDD hhmmss)
|
|
235
|
+
if (trimmed.length < 13) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
// Extract date and time parts
|
|
239
|
+
// Format: YYYYMMDD hhmmss
|
|
240
|
+
const datePart = trimmed.substring(0, 8); // YYYYMMDD
|
|
241
|
+
const timePart = trimmed.substring(8).trim(); // hhmmss (may have leading space)
|
|
242
|
+
// Validate lengths
|
|
243
|
+
if (datePart.length !== 8 || timePart.length !== 6) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
// Parse components
|
|
247
|
+
const year = parseInt(datePart.substring(0, 4), 10);
|
|
248
|
+
const month = parseInt(datePart.substring(4, 6), 10);
|
|
249
|
+
const day = parseInt(datePart.substring(6, 8), 10);
|
|
250
|
+
const hour = parseInt(timePart.substring(0, 2), 10);
|
|
251
|
+
const minute = parseInt(timePart.substring(2, 4), 10);
|
|
252
|
+
const second = parseInt(timePart.substring(4, 6), 10);
|
|
253
|
+
// Validate values
|
|
254
|
+
if (isNaN(year) || isNaN(month) || isNaN(day) ||
|
|
255
|
+
isNaN(hour) || isNaN(minute) || isNaN(second)) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
// Validate ranges
|
|
259
|
+
if (month < 1 || month > 12) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
if (day < 1 || day > 31) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
if (hour < 0 || hour > 23) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
if (minute < 0 || minute > 59) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
if (second < 0 || second > 59) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
// Create Date object and get timestamp
|
|
275
|
+
const date = new Date(year, month - 1, day, hour, minute, second);
|
|
276
|
+
const timestamp = date.getTime();
|
|
277
|
+
if (isNaN(timestamp)) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
return timestamp;
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getXYWebSocketManager } from "../client.js";
|
|
2
2
|
import { sendCommand } from "../formatter.js";
|
|
3
|
-
import {
|
|
4
|
-
import { logger } from "../utils/logger.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
5
4
|
/**
|
|
6
5
|
* XY search calendar event tool - searches calendar events on user's device.
|
|
7
6
|
* Returns matching events based on time range and optional title filter.
|
|
@@ -25,7 +24,12 @@ export const searchCalendarTool = {
|
|
|
25
24
|
- 查询晚上的日程:使用 18:00:00 到 23:59:59
|
|
26
25
|
- 查询某个时刻附近的日程:使用该时刻前后1小时的区间(例如:查询3点左右的日程,使用 14:00:00 到 16:00:00)
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
注意:
|
|
28
|
+
a. 该工具执行时间较长(最多60秒),请勿重复调用,超时或失败时最多重试一次。
|
|
29
|
+
b. 使用该工具之前需获取当前真实时间
|
|
30
|
+
|
|
31
|
+
回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。
|
|
32
|
+
`,
|
|
29
33
|
parameters: {
|
|
30
34
|
type: "object",
|
|
31
35
|
properties: {
|
|
@@ -45,19 +49,11 @@ export const searchCalendarTool = {
|
|
|
45
49
|
required: ["startTime", "endTime"],
|
|
46
50
|
},
|
|
47
51
|
async execute(toolCallId, params) {
|
|
48
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] 🚀 Starting execution`);
|
|
49
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
|
|
50
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - params:`, JSON.stringify(params));
|
|
51
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
52
52
|
// Validate parameters
|
|
53
53
|
if (!params.startTime || !params.endTime) {
|
|
54
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] ❌ Missing required parameters`);
|
|
55
54
|
throw new Error("Missing required parameters: startTime and endTime are required");
|
|
56
55
|
}
|
|
57
56
|
// Convert time strings to millisecond timestamps
|
|
58
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] 🕒 Converting time strings to timestamps...`);
|
|
59
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - startTime input: ${params.startTime}`);
|
|
60
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - endTime input: ${params.endTime}`);
|
|
61
57
|
// Parse YYYYMMDD hhmmss format
|
|
62
58
|
const parseTimeString = (timeStr) => {
|
|
63
59
|
// Remove any extra spaces and split
|
|
@@ -80,17 +76,6 @@ export const searchCalendarTool = {
|
|
|
80
76
|
const date = new Date(year, month, day, hours, minutes, seconds);
|
|
81
77
|
return date.getTime();
|
|
82
78
|
};
|
|
83
|
-
// Helper function to convert timestamp to YYYYMMDD hhmmss format
|
|
84
|
-
const formatTimestamp = (timestamp) => {
|
|
85
|
-
const date = new Date(timestamp);
|
|
86
|
-
const year = date.getFullYear();
|
|
87
|
-
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
88
|
-
const day = String(date.getDate()).padStart(2, '0');
|
|
89
|
-
const hours = String(date.getHours()).padStart(2, '0');
|
|
90
|
-
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
91
|
-
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
92
|
-
return `${year}${month}${day} ${hours}${minutes}${seconds}`;
|
|
93
|
-
};
|
|
94
79
|
let startTimeMs;
|
|
95
80
|
let endTimeMs;
|
|
96
81
|
try {
|
|
@@ -98,42 +83,26 @@ export const searchCalendarTool = {
|
|
|
98
83
|
endTimeMs = parseTimeString(params.endTime);
|
|
99
84
|
}
|
|
100
85
|
catch (error) {
|
|
101
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] ❌ Time parsing error:`, error);
|
|
102
86
|
throw new Error(`Invalid time format. Required format: YYYYMMDD hhmmss (e.g., 20240115 143000). Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
103
87
|
}
|
|
104
88
|
if (isNaN(startTimeMs) || isNaN(endTimeMs)) {
|
|
105
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] ❌ Invalid time format`);
|
|
106
89
|
throw new Error("Invalid time format. Required format: YYYYMMDD hhmmss (e.g., 20240115 143000)");
|
|
107
90
|
}
|
|
108
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] ✅ Time conversion successful`);
|
|
109
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - startTime timestamp: ${startTimeMs}`);
|
|
110
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - endTime timestamp: ${endTimeMs}`);
|
|
111
91
|
// Get session context
|
|
112
|
-
|
|
113
|
-
const sessionContext = getLatestSessionContext();
|
|
92
|
+
const sessionContext = getCurrentSessionContext();
|
|
114
93
|
if (!sessionContext) {
|
|
115
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] ❌ FAILED: No active session found!`);
|
|
116
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
|
|
117
94
|
throw new Error("No active XY session found. Search calendar tool can only be used during an active conversation.");
|
|
118
95
|
}
|
|
119
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] ✅ Session context found`);
|
|
120
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
121
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
122
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
123
96
|
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
124
97
|
// Get WebSocket manager
|
|
125
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] 🔌 Getting WebSocket manager...`);
|
|
126
98
|
const wsManager = getXYWebSocketManager(config);
|
|
127
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] ✅ WebSocket manager obtained`);
|
|
128
99
|
// Build SearchCalendarEvent command
|
|
129
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] 📦 Building SearchCalendarEvent command...`);
|
|
130
100
|
// Build intentParam with timeInterval and optional title
|
|
131
101
|
const intentParam = {
|
|
132
102
|
timeInterval: [startTimeMs, endTimeMs],
|
|
133
103
|
};
|
|
134
104
|
if (params.title) {
|
|
135
105
|
intentParam.title = params.title;
|
|
136
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - Including title filter: ${params.title}`);
|
|
137
106
|
}
|
|
138
107
|
const command = {
|
|
139
108
|
header: {
|
|
@@ -168,76 +137,35 @@ export const searchCalendarTool = {
|
|
|
168
137
|
},
|
|
169
138
|
};
|
|
170
139
|
// Send command and wait for response (60 second timeout)
|
|
171
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] ⏳ Setting up promise to wait for calendar search response...`);
|
|
172
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - Timeout: 60 seconds`);
|
|
173
140
|
return new Promise((resolve, reject) => {
|
|
174
141
|
const timeout = setTimeout(() => {
|
|
175
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] ⏰ Timeout: No response received within 60 seconds`);
|
|
176
142
|
wsManager.off("data-event", handler);
|
|
177
143
|
reject(new Error("检索日程超时(60秒)"));
|
|
178
144
|
}, 60000);
|
|
179
145
|
// Listen for data events from WebSocket
|
|
180
146
|
const handler = (event) => {
|
|
181
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
182
147
|
if (event.intentName === "SearchCalendarEvent") {
|
|
183
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] 🎯 SearchCalendarEvent event received`);
|
|
184
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - status: ${event.status}`);
|
|
185
148
|
clearTimeout(timeout);
|
|
186
149
|
wsManager.off("data-event", handler);
|
|
187
150
|
if (event.status === "success" && event.outputs) {
|
|
188
|
-
|
|
189
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - outputs:`, JSON.stringify(event.outputs));
|
|
190
|
-
// Check for error code in outputs
|
|
191
|
-
if (event.outputs.retErrCode && event.outputs.retErrCode !== "0") {
|
|
192
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] ❌ Device returned error`);
|
|
193
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] - retErrCode: ${event.outputs.retErrCode}`);
|
|
194
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] - errMsg: ${event.outputs.errMsg || "Unknown error"}`);
|
|
195
|
-
reject(new Error(`检索日程失败: ${event.outputs.errMsg || "未知错误"} (错误代码: ${event.outputs.retErrCode})`));
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
// Return the result directly as requested
|
|
199
|
-
const result = event.outputs.result;
|
|
200
|
-
// Ensure result is not undefined
|
|
201
|
-
if (result === undefined) {
|
|
202
|
-
logger.warn(`[SEARCH_CALENDAR_TOOL] ⚠️ Result is undefined, returning empty result`);
|
|
203
|
-
}
|
|
204
|
-
// Convert dtStart and dtEnd from timestamps to YYYYMMDD hhmmss format
|
|
205
|
-
if (result && result.items && Array.isArray(result.items)) {
|
|
206
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] 🔄 Converting timestamps to formatted dates...`);
|
|
207
|
-
result.items = result.items.map((item) => {
|
|
208
|
-
const formattedItem = { ...item };
|
|
209
|
-
if (item.dtStart) {
|
|
210
|
-
formattedItem.dtStart = formatTimestamp(item.dtStart);
|
|
211
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - dtStart: ${item.dtStart} -> ${formattedItem.dtStart}`);
|
|
212
|
-
}
|
|
213
|
-
if (item.dtEnd) {
|
|
214
|
-
formattedItem.dtEnd = formatTimestamp(item.dtEnd);
|
|
215
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] - dtEnd: ${item.dtEnd} -> ${formattedItem.dtEnd}`);
|
|
216
|
-
}
|
|
217
|
-
return formattedItem;
|
|
218
|
-
});
|
|
219
|
-
}
|
|
151
|
+
// 成功,直接返回完整的 event.outputs JSON 字符串
|
|
220
152
|
resolve({
|
|
221
153
|
content: [
|
|
222
154
|
{
|
|
223
155
|
type: "text",
|
|
224
|
-
text:
|
|
156
|
+
text: JSON.stringify(event.outputs),
|
|
225
157
|
},
|
|
226
158
|
],
|
|
227
159
|
});
|
|
228
160
|
}
|
|
229
161
|
else {
|
|
230
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] ❌ Calendar event search failed`);
|
|
231
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] - status: ${event.status}`);
|
|
232
162
|
reject(new Error(`检索日程失败: ${event.status}`));
|
|
233
163
|
}
|
|
234
164
|
}
|
|
235
165
|
};
|
|
236
166
|
// Register event handler
|
|
237
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] 📡 Registering data-event handler on WebSocket manager`);
|
|
238
167
|
wsManager.on("data-event", handler);
|
|
239
168
|
// Send the command
|
|
240
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] 📤 Sending SearchCalendarEvent command...`);
|
|
241
169
|
sendCommand({
|
|
242
170
|
config,
|
|
243
171
|
sessionId,
|
|
@@ -246,10 +174,8 @@ export const searchCalendarTool = {
|
|
|
246
174
|
command,
|
|
247
175
|
})
|
|
248
176
|
.then(() => {
|
|
249
|
-
logger.log(`[SEARCH_CALENDAR_TOOL] ✅ Command sent successfully, waiting for response...`);
|
|
250
177
|
})
|
|
251
178
|
.catch((error) => {
|
|
252
|
-
logger.error(`[SEARCH_CALENDAR_TOOL] ❌ Failed to send command:`, error);
|
|
253
179
|
clearTimeout(timeout);
|
|
254
180
|
wsManager.off("data-event", handler);
|
|
255
181
|
reject(error);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getXYWebSocketManager } from "../client.js";
|
|
2
2
|
import { sendCommand } from "../formatter.js";
|
|
3
|
-
import {
|
|
4
|
-
import { logger } from "../utils/logger.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
5
4
|
/**
|
|
6
5
|
* XY search contact tool - searches contacts on user's device.
|
|
7
6
|
* Returns matching contact information based on name.
|
|
@@ -21,34 +20,19 @@ export const searchContactTool = {
|
|
|
21
20
|
required: ["name"],
|
|
22
21
|
},
|
|
23
22
|
async execute(toolCallId, params) {
|
|
24
|
-
logger.log(`[SEARCH_CONTACT_TOOL] 🚀 Starting execution`);
|
|
25
|
-
logger.log(`[SEARCH_CONTACT_TOOL] - toolCallId: ${toolCallId}`);
|
|
26
|
-
logger.log(`[SEARCH_CONTACT_TOOL] - params:`, JSON.stringify(params));
|
|
27
|
-
logger.log(`[SEARCH_CONTACT_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
28
23
|
// Validate parameters
|
|
29
24
|
if (!params.name) {
|
|
30
|
-
logger.error(`[SEARCH_CONTACT_TOOL] ❌ Missing required parameter: name`);
|
|
31
25
|
throw new Error("Missing required parameter: name is required");
|
|
32
26
|
}
|
|
33
27
|
// Get session context
|
|
34
|
-
|
|
35
|
-
const sessionContext = getLatestSessionContext();
|
|
28
|
+
const sessionContext = getCurrentSessionContext();
|
|
36
29
|
if (!sessionContext) {
|
|
37
|
-
logger.error(`[SEARCH_CONTACT_TOOL] ❌ FAILED: No active session found!`);
|
|
38
|
-
logger.error(`[SEARCH_CONTACT_TOOL] - toolCallId: ${toolCallId}`);
|
|
39
30
|
throw new Error("No active XY session found. Search contact tool can only be used during an active conversation.");
|
|
40
31
|
}
|
|
41
|
-
logger.log(`[SEARCH_CONTACT_TOOL] ✅ Session context found`);
|
|
42
|
-
logger.log(`[SEARCH_CONTACT_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
43
|
-
logger.log(`[SEARCH_CONTACT_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
44
|
-
logger.log(`[SEARCH_CONTACT_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
45
32
|
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
46
33
|
// Get WebSocket manager
|
|
47
|
-
logger.log(`[SEARCH_CONTACT_TOOL] 🔌 Getting WebSocket manager...`);
|
|
48
34
|
const wsManager = getXYWebSocketManager(config);
|
|
49
|
-
logger.log(`[SEARCH_CONTACT_TOOL] ✅ WebSocket manager obtained`);
|
|
50
35
|
// Build SearchContactLocal command
|
|
51
|
-
logger.log(`[SEARCH_CONTACT_TOOL] 📦 Building SearchContactLocal command...`);
|
|
52
36
|
const command = {
|
|
53
37
|
header: {
|
|
54
38
|
namespace: "Common",
|
|
@@ -83,70 +67,35 @@ export const searchContactTool = {
|
|
|
83
67
|
},
|
|
84
68
|
};
|
|
85
69
|
// Send command and wait for response (60 second timeout)
|
|
86
|
-
logger.log(`[SEARCH_CONTACT_TOOL] ⏳ Setting up promise to wait for contact search response...`);
|
|
87
|
-
logger.log(`[SEARCH_CONTACT_TOOL] - Timeout: 60 seconds`);
|
|
88
70
|
return new Promise((resolve, reject) => {
|
|
89
71
|
const timeout = setTimeout(() => {
|
|
90
|
-
logger.error(`[SEARCH_CONTACT_TOOL] ⏰ Timeout: No response received within 60 seconds`);
|
|
91
72
|
wsManager.off("data-event", handler);
|
|
92
73
|
reject(new Error("搜索联系人超时(60秒)"));
|
|
93
74
|
}, 60000);
|
|
94
75
|
// Listen for data events from WebSocket
|
|
95
76
|
const handler = (event) => {
|
|
96
|
-
logger.log(`[SEARCH_CONTACT_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
97
77
|
if (event.intentName === "SearchContactLocal") {
|
|
98
|
-
logger.log(`[SEARCH_CONTACT_TOOL] 🎯 SearchContactLocal event received`);
|
|
99
|
-
logger.log(`[SEARCH_CONTACT_TOOL] - status: ${event.status}`);
|
|
100
78
|
clearTimeout(timeout);
|
|
101
79
|
wsManager.off("data-event", handler);
|
|
102
80
|
if (event.status === "success" && event.outputs) {
|
|
103
|
-
|
|
104
|
-
logger.log(`[SEARCH_CONTACT_TOOL] - outputs:`, JSON.stringify(event.outputs));
|
|
105
|
-
// Check for error code first
|
|
106
|
-
if (event.outputs.retErrCode && event.outputs.retErrCode !== "0") {
|
|
107
|
-
logger.error(`[SEARCH_CONTACT_TOOL] ❌ Search failed with error code: ${event.outputs.retErrCode}`);
|
|
108
|
-
logger.error(`[SEARCH_CONTACT_TOOL] - errMsg: ${event.outputs.errMsg}`);
|
|
109
|
-
reject(new Error(`搜索联系人失败: ${event.outputs.errMsg || '未知错误'} (错误码: ${event.outputs.retErrCode})`));
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
// Get the result
|
|
113
|
-
const result = event.outputs.result;
|
|
114
|
-
// Check if result exists
|
|
115
|
-
if (!result) {
|
|
116
|
-
logger.warn(`[SEARCH_CONTACT_TOOL] ⚠️ No result found for name "${params.name}"`);
|
|
117
|
-
resolve({
|
|
118
|
-
content: [
|
|
119
|
-
{
|
|
120
|
-
type: "text",
|
|
121
|
-
text: JSON.stringify({ items: [], message: "未找到匹配的联系人" }),
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
});
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
logger.log(`[SEARCH_CONTACT_TOOL] 📊 Contacts found: ${result?.items?.length || 0} results for name "${params.name}"`);
|
|
128
|
-
// Return the result with valid string content
|
|
81
|
+
// 成功,直接返回完整的 event.outputs JSON 字符串
|
|
129
82
|
resolve({
|
|
130
83
|
content: [
|
|
131
84
|
{
|
|
132
85
|
type: "text",
|
|
133
|
-
text: JSON.stringify(
|
|
86
|
+
text: JSON.stringify(event.outputs),
|
|
134
87
|
},
|
|
135
88
|
],
|
|
136
89
|
});
|
|
137
90
|
}
|
|
138
91
|
else {
|
|
139
|
-
logger.error(`[SEARCH_CONTACT_TOOL] ❌ Contact search failed`);
|
|
140
|
-
logger.error(`[SEARCH_CONTACT_TOOL] - status: ${event.status}`);
|
|
141
92
|
reject(new Error(`搜索联系人失败: ${event.status}`));
|
|
142
93
|
}
|
|
143
94
|
}
|
|
144
95
|
};
|
|
145
96
|
// Register event handler
|
|
146
|
-
logger.log(`[SEARCH_CONTACT_TOOL] 📡 Registering data-event handler on WebSocket manager`);
|
|
147
97
|
wsManager.on("data-event", handler);
|
|
148
98
|
// Send the command
|
|
149
|
-
logger.log(`[SEARCH_CONTACT_TOOL] 📤 Sending SearchContactLocal command...`);
|
|
150
99
|
sendCommand({
|
|
151
100
|
config,
|
|
152
101
|
sessionId,
|
|
@@ -155,10 +104,8 @@ export const searchContactTool = {
|
|
|
155
104
|
command,
|
|
156
105
|
})
|
|
157
106
|
.then(() => {
|
|
158
|
-
logger.log(`[SEARCH_CONTACT_TOOL] ✅ Command sent successfully, waiting for response...`);
|
|
159
107
|
})
|
|
160
108
|
.catch((error) => {
|
|
161
|
-
logger.error(`[SEARCH_CONTACT_TOOL] ❌ Failed to send command:`, error);
|
|
162
109
|
clearTimeout(timeout);
|
|
163
110
|
wsManager.off("data-event", handler);
|
|
164
111
|
reject(error);
|