@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.
Files changed (86) hide show
  1. package/dist/index.d.ts +0 -2
  2. package/dist/index.js +48 -2
  3. package/dist/src/bot.js +81 -28
  4. package/dist/src/channel.js +33 -7
  5. package/dist/src/client.js +11 -27
  6. package/dist/src/config.js +2 -2
  7. package/dist/src/cspl/call-api.d.ts +3 -0
  8. package/dist/src/cspl/call-api.js +107 -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 +92 -0
  17. package/dist/src/formatter.d.ts +14 -0
  18. package/dist/src/formatter.js +49 -25
  19. package/dist/src/heartbeat.js +0 -4
  20. package/dist/src/monitor.js +12 -0
  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 +52 -6
  25. package/dist/src/parser.d.ts +7 -0
  26. package/dist/src/parser.js +22 -0
  27. package/dist/src/push.d.ts +8 -1
  28. package/dist/src/push.js +30 -22
  29. package/dist/src/reply-dispatcher.js +39 -5
  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 +7 -40
  35. package/dist/src/tools/call-phone-tool.d.ts +5 -0
  36. package/dist/src/tools/call-phone-tool.js +142 -0
  37. package/dist/src/tools/create-alarm-tool.d.ts +7 -0
  38. package/dist/src/tools/create-alarm-tool.js +349 -0
  39. package/dist/src/tools/delete-alarm-tool.d.ts +11 -0
  40. package/dist/src/tools/delete-alarm-tool.js +174 -0
  41. package/dist/src/tools/image-reading-tool.d.ts +5 -0
  42. package/dist/src/tools/image-reading-tool.js +328 -0
  43. package/dist/src/tools/location-tool.js +8 -42
  44. package/dist/src/tools/modify-alarm-tool.d.ts +9 -0
  45. package/dist/src/tools/modify-alarm-tool.js +365 -0
  46. package/dist/src/tools/modify-note-tool.js +5 -43
  47. package/dist/src/tools/note-tool.js +33 -24
  48. package/dist/src/tools/search-alarm-tool.d.ts +8 -0
  49. package/dist/src/tools/search-alarm-tool.js +285 -0
  50. package/dist/src/tools/search-calendar-tool.js +10 -84
  51. package/dist/src/tools/search-contact-tool.js +4 -57
  52. package/dist/src/tools/search-file-tool.d.ts +5 -0
  53. package/dist/src/tools/search-file-tool.js +128 -0
  54. package/dist/src/tools/search-message-tool.d.ts +5 -0
  55. package/dist/src/tools/search-message-tool.js +116 -0
  56. package/dist/src/tools/search-note-tool.js +6 -24
  57. package/dist/src/tools/search-photo-gallery-tool.js +40 -61
  58. package/dist/src/tools/send-file-to-user-tool.d.ts +5 -0
  59. package/dist/src/tools/send-file-to-user-tool.js +279 -0
  60. package/dist/src/tools/send-message-tool.d.ts +5 -0
  61. package/dist/src/tools/send-message-tool.js +138 -0
  62. package/dist/src/tools/session-manager.d.ts +15 -0
  63. package/dist/src/tools/session-manager.js +43 -31
  64. package/dist/src/tools/upload-file-tool.d.ts +13 -0
  65. package/dist/src/tools/upload-file-tool.js +216 -0
  66. package/dist/src/tools/upload-photo-tool.js +2 -44
  67. package/dist/src/tools/view-push-result-tool.d.ts +5 -0
  68. package/dist/src/tools/view-push-result-tool.js +107 -0
  69. package/dist/src/tools/xiaoyi-collection-tool.d.ts +5 -0
  70. package/dist/src/tools/xiaoyi-collection-tool.js +112 -0
  71. package/dist/src/tools/xiaoyi-gui-tool.js +2 -36
  72. package/dist/src/trigger-handler.d.ts +22 -0
  73. package/dist/src/trigger-handler.js +59 -0
  74. package/dist/src/types.d.ts +1 -8
  75. package/dist/src/types.js +4 -0
  76. package/dist/src/utils/pushdata-manager.d.ts +28 -0
  77. package/dist/src/utils/pushdata-manager.js +171 -0
  78. package/dist/src/utils/pushid-manager.d.ts +12 -0
  79. package/dist/src/utils/pushid-manager.js +105 -0
  80. package/dist/src/websocket.d.ts +25 -31
  81. package/dist/src/websocket.js +233 -271
  82. package/package.json +2 -2
  83. package/dist/src/tools/search-photo-tool.d.ts +0 -9
  84. package/dist/src/tools/search-photo-tool.js +0 -270
  85. package/dist/src/utils/session.d.ts +0 -34
  86. package/dist/src/utils/session.js +0 -50
@@ -0,0 +1,365 @@
1
+ import { getXYWebSocketManager } from "../client.js";
2
+ import { sendCommand } from "../formatter.js";
3
+ import { getCurrentSessionContext } from "./session-manager.js";
4
+ // Enum definitions for alarm parameters (same as create-alarm-tool)
5
+ const ALARM_SNOOZE_DURATION_VALUES = [5, 10, 15, 20, 25, 30];
6
+ const ALARM_SNOOZE_TOTAL_VALUES = [0, 1, 3, 5, 10];
7
+ const ALARM_RING_DURATION_VALUES = [1, 5, 10, 15, 20, 30];
8
+ const DAYS_OF_WAKE_TYPE_VALUES = [0, 1, 2, 3, 4];
9
+ const DAYS_OF_WEEK_VALUES = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
10
+ /**
11
+ * XY modify alarm tool - modifies an existing alarm on user's device.
12
+ * Requires entityId from search_alarm or create_alarm tool.
13
+ *
14
+ * Prerequisites:
15
+ * 1. Call search_alarm or create_alarm tool first to get entityId
16
+ * 2. Use the entityId to identify which alarm to modify
17
+ */
18
+ export const modifyAlarmTool = {
19
+ name: "modify_alarm",
20
+ label: "Modify Alarm",
21
+ description: `修改用户设备上已存在的闹钟。
22
+
23
+ 必需参数:
24
+ - entityId: 闹钟的唯一标识符,必须先通过 search_alarm 或 create_alarm 工具获取
25
+
26
+ 可选参数(与创建闹钟的参数完全一致):
27
+ - alarmTime: 闹钟时间,格式必须为:YYYYMMDD hhmmss(例如:20240315 143000)
28
+ - alarmTitle: 闹钟名称/标题
29
+ - alarmState: 闹钟开启状态,0=关闭,1=开启
30
+ - alarmSnoozeDuration: 小睡间隔(分钟),枚举值:5,10,15,20,25,30
31
+ - alarmSnoozeTotal: 再响次数,枚举值:0,1,3,5,10
32
+ - alarmRingDuration: 响铃时长(分钟),枚举值:1,5,10,15,20,30
33
+ - daysOfWakeType: 闹钟响铃类型,枚举值:0=单次,1=法定节假日,2=每天,3=自定义,4=法定工作日
34
+ - daysOfWeek: 自定义响铃星期,仅当daysOfWakeType=3(自定义时间)时必需且有效,其他情况不要传递此参数。数组或JSON字符串,枚举值:Mon,Tue,Wed,Thu,Fri,Sat,Sun。注意:仅支持长度为1的数组,如果需要一周中不同的几天,需要多次调用此工具
35
+
36
+ 使用流程:
37
+ 1. 先调用 search_alarm 工具查询闹钟,获取 entityId,
38
+ 2. 调用此工具修改闹钟,传入 entityId 和需要修改的参数
39
+ 3. 其余不涉及需改的参数,如果search_alarm 或 create_alarm的结果中有相应的值,需要一并填上,需要与原有的保持一致,防止不填采用默认值
40
+
41
+ 注意事项:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。
42
+
43
+ 回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。`,
44
+ parameters: {
45
+ type: "object",
46
+ properties: {
47
+ entityId: {
48
+ type: "string",
49
+ description: "闹钟的唯一标识符,必须先通过 search_alarm 或 create_alarm 工具获取",
50
+ },
51
+ alarmTime: {
52
+ type: "string",
53
+ description: "闹钟时间,格式必须为:YYYYMMDD hhmmss(例如:20240315 143000)",
54
+ },
55
+ alarmTitle: {
56
+ type: "string",
57
+ description: "闹钟名称/标题",
58
+ },
59
+ alarmState: {
60
+ type: "number",
61
+ description: "闹钟开启状态:0=关闭,1=开启",
62
+ },
63
+ alarmSnoozeDuration: {
64
+ type: "number",
65
+ description: "小睡间隔(分钟),枚举值:5,10,15,20,25,30",
66
+ },
67
+ alarmSnoozeTotal: {
68
+ type: "number",
69
+ description: "再响次数,枚举值:0,1,3,5,10",
70
+ },
71
+ alarmRingDuration: {
72
+ type: "number",
73
+ description: "响铃时长(分钟),枚举值:1,5,10,15,20,30",
74
+ },
75
+ daysOfWakeType: {
76
+ type: "number",
77
+ description: "闹钟响铃类型:0=单次,1=法定节假日,2=每天,3=自定义,4=法定工作日",
78
+ },
79
+ daysOfWeek: {
80
+ // 不指定 type,允许传入数组或 JSON 字符串
81
+ // 具体的类型验证和转换在 execute 函数内部进行
82
+ description: "自定义响铃星期(仅当daysOfWakeType=3时需要,其他情况不要传递),数组或JSON字符串,枚举值:Mon,Tue,Wed,Thu,Fri,Sat,Sun。注意:仅支持长度为1的数组,如果需要一周中不同的几天,需要多次调用此工具",
83
+ },
84
+ },
85
+ required: ["entityId"],
86
+ },
87
+ async execute(toolCallId, params) {
88
+ // ===== Validate required parameter: entityId =====
89
+ if (!params.entityId || typeof params.entityId !== "string") {
90
+ throw new Error("Missing required parameter: entityId must be a string obtained from search_alarm or create_alarm");
91
+ }
92
+ // ===== Build intentParam with provided parameters =====
93
+ const intentParam = {
94
+ entityName: "Alarm",
95
+ entityId: params.entityId,
96
+ };
97
+ // Parse and convert alarmTime if provided
98
+ if (params.alarmTime !== undefined && params.alarmTime !== null) {
99
+ if (typeof params.alarmTime !== "string") {
100
+ throw new Error("alarmTime must be a string in format YYYYMMDD hhmmss");
101
+ }
102
+ const alarmTimeMs = parseAlarmTimeToTimestamp(params.alarmTime);
103
+ if (alarmTimeMs === null) {
104
+ throw new Error("Invalid alarmTime format. Required format: YYYYMMDD hhmmss (e.g., 20240315 143000)");
105
+ }
106
+ intentParam.alarmTime = alarmTimeMs;
107
+ }
108
+ // Add alarmTitle if provided
109
+ if (params.alarmTitle !== undefined && params.alarmTitle !== null) {
110
+ if (typeof params.alarmTitle !== "string") {
111
+ throw new Error("alarmTitle must be a string");
112
+ }
113
+ intentParam.alarmTitle = params.alarmTitle;
114
+ }
115
+ // Add alarmState if provided
116
+ if (params.alarmState !== undefined && params.alarmState !== null) {
117
+ if (typeof params.alarmState !== "number") {
118
+ throw new Error("alarmState must be a number");
119
+ }
120
+ if (!ALARM_STATE_VALUES.includes(params.alarmState)) {
121
+ throw new Error(`alarmState must be one of: ${ALARM_STATE_VALUES.join(", ")}`);
122
+ }
123
+ intentParam.alarmState = params.alarmState;
124
+ }
125
+ // Add alarmSnoozeDuration if provided
126
+ if (params.alarmSnoozeDuration !== undefined && params.alarmSnoozeDuration !== null) {
127
+ if (typeof params.alarmSnoozeDuration !== "number") {
128
+ throw new Error("alarmSnoozeDuration must be a number");
129
+ }
130
+ if (!ALARM_SNOOZE_DURATION_VALUES.includes(params.alarmSnoozeDuration)) {
131
+ throw new Error(`alarmSnoozeDuration must be one of: ${ALARM_SNOOZE_DURATION_VALUES.join(", ")}`);
132
+ }
133
+ intentParam.alarmSnoozeDuration = params.alarmSnoozeDuration;
134
+ }
135
+ // Add alarmSnoozeTotal if provided
136
+ if (params.alarmSnoozeTotal !== undefined && params.alarmSnoozeTotal !== null) {
137
+ if (typeof params.alarmSnoozeTotal !== "number") {
138
+ throw new Error("alarmSnoozeTotal must be a number");
139
+ }
140
+ if (!ALARM_SNOOZE_TOTAL_VALUES.includes(params.alarmSnoozeTotal)) {
141
+ throw new Error(`alarmSnoozeTotal must be one of: ${ALARM_SNOOZE_TOTAL_VALUES.join(", ")}`);
142
+ }
143
+ intentParam.alarmSnoozeTotal = params.alarmSnoozeTotal;
144
+ }
145
+ // Add alarmRingDuration if provided
146
+ if (params.alarmRingDuration !== undefined && params.alarmRingDuration !== null) {
147
+ if (typeof params.alarmRingDuration !== "number") {
148
+ throw new Error("alarmRingDuration must be a number");
149
+ }
150
+ if (!ALARM_RING_DURATION_VALUES.includes(params.alarmRingDuration)) {
151
+ throw new Error(`alarmRingDuration must be one of: ${ALARM_RING_DURATION_VALUES.join(", ")}`);
152
+ }
153
+ intentParam.alarmRingDuration = params.alarmRingDuration;
154
+ }
155
+ // Add daysOfWakeType if provided
156
+ if (params.daysOfWakeType !== undefined && params.daysOfWakeType !== null) {
157
+ if (typeof params.daysOfWakeType !== "number") {
158
+ throw new Error("daysOfWakeType must be a number");
159
+ }
160
+ if (!DAYS_OF_WAKE_TYPE_VALUES.includes(params.daysOfWakeType)) {
161
+ throw new Error(`daysOfWakeType must be one of: ${DAYS_OF_WAKE_TYPE_VALUES.join(", ")}`);
162
+ }
163
+ intentParam.daysOfWakeType = params.daysOfWakeType;
164
+ }
165
+ // Add daysOfWeek if provided - only valid when daysOfWakeType is 3
166
+ if (params.daysOfWeek !== undefined && params.daysOfWeek !== null) {
167
+ // Check if daysOfWakeType is 3 or will be set to 3
168
+ const targetDaysOfWakeType = params.daysOfWakeType !== undefined ? params.daysOfWakeType : null;
169
+ if (targetDaysOfWakeType !== null && targetDaysOfWakeType !== 3) {
170
+ // Skip processing daysOfWeek when daysOfWakeType is not 3
171
+ }
172
+ else {
173
+ // ===== 参数规范化:兼容数组和 JSON 字符串 =====
174
+ let normalizedDaysOfWeek = null;
175
+ // 情况1: 已经是数组
176
+ if (Array.isArray(params.daysOfWeek)) {
177
+ normalizedDaysOfWeek = params.daysOfWeek;
178
+ }
179
+ // 情况2: 是字符串,尝试解析为 JSON 数组
180
+ else if (typeof params.daysOfWeek === 'string') {
181
+ try {
182
+ const parsed = JSON.parse(params.daysOfWeek);
183
+ if (Array.isArray(parsed)) {
184
+ normalizedDaysOfWeek = parsed;
185
+ }
186
+ else {
187
+ throw new Error("daysOfWeek must be an array or a JSON string representing an array");
188
+ }
189
+ }
190
+ catch (parseError) {
191
+ throw new Error(`daysOfWeek must be a valid JSON array string. Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
192
+ }
193
+ }
194
+ // 情况3: 其他类型,报错
195
+ else {
196
+ throw new Error(`daysOfWeek must be an array or a JSON string, got ${typeof params.daysOfWeek}`);
197
+ }
198
+ // 验证数组非空
199
+ if (!normalizedDaysOfWeek || normalizedDaysOfWeek.length === 0) {
200
+ throw new Error("daysOfWeek array cannot be empty");
201
+ }
202
+ // 验证数组长度必须为1
203
+ if (normalizedDaysOfWeek.length !== 1) {
204
+ throw new Error("daysOfWeek 仅支持长度为1的数组。如果需要一周中不同的几天,需要多次调用此工具");
205
+ }
206
+ // Validate each day
207
+ for (const day of normalizedDaysOfWeek) {
208
+ if (typeof day !== "string" || !DAYS_OF_WEEK_VALUES.includes(day)) {
209
+ throw new Error(`daysOfWeek must contain only: ${DAYS_OF_WEEK_VALUES.join(", ")}`);
210
+ }
211
+ }
212
+ intentParam.daysOfWeek = normalizedDaysOfWeek;
213
+ }
214
+ }
215
+ // Get session context
216
+ const sessionContext = getCurrentSessionContext();
217
+ if (!sessionContext) {
218
+ throw new Error("No active XY session found. Modify alarm tool can only be used during an active conversation.");
219
+ }
220
+ const { config, sessionId, taskId, messageId } = sessionContext;
221
+ // Get WebSocket manager
222
+ const wsManager = getXYWebSocketManager(config);
223
+ // Build ModifyAlarm command
224
+ const command = {
225
+ header: {
226
+ namespace: "Common",
227
+ name: "Action",
228
+ },
229
+ payload: {
230
+ cardParam: {},
231
+ executeParam: {
232
+ executeMode: "background",
233
+ intentName: "ModifyAlarm",
234
+ bundleName: "com.huawei.hmos.clock",
235
+ needUnlock: true,
236
+ actionResponse: true,
237
+ appType: "OHOS_APP",
238
+ timeOut: 5,
239
+ intentParam: intentParam,
240
+ permissionId: [],
241
+ achieveType: "INTENT",
242
+ },
243
+ responses: [
244
+ {
245
+ resultCode: "",
246
+ displayText: "",
247
+ ttsText: "",
248
+ },
249
+ ],
250
+ needUploadResult: true,
251
+ noHalfPage: false,
252
+ pageControlRelated: false,
253
+ },
254
+ };
255
+ // Send command and wait for response (60 second timeout)
256
+ return new Promise((resolve, reject) => {
257
+ const timeout = setTimeout(() => {
258
+ wsManager.off("data-event", handler);
259
+ reject(new Error("修改闹钟超时(60秒)"));
260
+ }, 60000);
261
+ // Listen for data events from WebSocket
262
+ const handler = (event) => {
263
+ if (event.intentName === "ModifyAlarm") {
264
+ clearTimeout(timeout);
265
+ wsManager.off("data-event", handler);
266
+ if (event.status === "success" && event.outputs) {
267
+ // 成功,直接返回完整的 event.outputs JSON 字符串
268
+ resolve({
269
+ content: [
270
+ {
271
+ type: "text",
272
+ text: JSON.stringify(event.outputs),
273
+ },
274
+ ],
275
+ });
276
+ }
277
+ else {
278
+ reject(new Error(`修改闹钟失败: ${event.status}`));
279
+ }
280
+ }
281
+ };
282
+ // Register event handler
283
+ wsManager.on("data-event", handler);
284
+ // Send the command
285
+ sendCommand({
286
+ config,
287
+ sessionId,
288
+ taskId,
289
+ messageId,
290
+ command,
291
+ })
292
+ .then(() => {
293
+ })
294
+ .catch((error) => {
295
+ clearTimeout(timeout);
296
+ wsManager.off("data-event", handler);
297
+ reject(error);
298
+ });
299
+ });
300
+ },
301
+ };
302
+ // Enum for alarm state
303
+ const ALARM_STATE_VALUES = [0, 1];
304
+ /**
305
+ * Parse alarmTime string (YYYYMMDD hhmmss) to timestamp in milliseconds
306
+ * @param alarmTime - Time string in format "YYYYMMDD hhmmss" (e.g., "20240315 143000")
307
+ * @returns Timestamp in milliseconds, or null if parsing fails
308
+ */
309
+ function parseAlarmTimeToTimestamp(alarmTime) {
310
+ try {
311
+ // Expected format: YYYYMMDD hhmmss
312
+ // Example: 20240315 143000
313
+ const trimmed = alarmTime.trim();
314
+ // Check basic format (should have at least 13 characters: YYYYMMDD hhmmss)
315
+ if (trimmed.length < 13) {
316
+ return null;
317
+ }
318
+ // Extract date and time parts
319
+ // Format: YYYYMMDD hhmmss
320
+ const datePart = trimmed.substring(0, 8); // YYYYMMDD
321
+ const timePart = trimmed.substring(8).trim(); // hhmmss (may have leading space)
322
+ // Validate lengths
323
+ if (datePart.length !== 8 || timePart.length !== 6) {
324
+ return null;
325
+ }
326
+ // Parse components
327
+ const year = parseInt(datePart.substring(0, 4), 10);
328
+ const month = parseInt(datePart.substring(4, 6), 10);
329
+ const day = parseInt(datePart.substring(6, 8), 10);
330
+ const hour = parseInt(timePart.substring(0, 2), 10);
331
+ const minute = parseInt(timePart.substring(2, 4), 10);
332
+ const second = parseInt(timePart.substring(4, 6), 10);
333
+ // Validate values
334
+ if (isNaN(year) || isNaN(month) || isNaN(day) ||
335
+ isNaN(hour) || isNaN(minute) || isNaN(second)) {
336
+ return null;
337
+ }
338
+ // Validate ranges
339
+ if (month < 1 || month > 12) {
340
+ return null;
341
+ }
342
+ if (day < 1 || day > 31) {
343
+ return null;
344
+ }
345
+ if (hour < 0 || hour > 23) {
346
+ return null;
347
+ }
348
+ if (minute < 0 || minute > 59) {
349
+ return null;
350
+ }
351
+ if (second < 0 || second > 59) {
352
+ return null;
353
+ }
354
+ // Create Date object and get timestamp
355
+ const date = new Date(year, month - 1, day, hour, minute, second);
356
+ const timestamp = date.getTime();
357
+ if (isNaN(timestamp)) {
358
+ return null;
359
+ }
360
+ return timestamp;
361
+ }
362
+ catch (error) {
363
+ return null;
364
+ }
365
+ }
@@ -1,7 +1,6 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { sendCommand } from "../formatter.js";
3
- import { getLatestSessionContext } from "./session-manager.js";
4
- import { logger } from "../utils/logger.js";
3
+ import { getCurrentSessionContext } from "./session-manager.js";
5
4
  /**
6
5
  * XY modify note tool - appends content to an existing note on user's device.
7
6
  * Requires entityId from search_notes tool as prerequisite.
@@ -13,7 +12,7 @@ import { logger } from "../utils/logger.js";
13
12
  export const modifyNoteTool = {
14
13
  name: "modify_note",
15
14
  label: "Modify Note",
16
- description: "在指定备忘录中追加新内容。使用前必须先调用 search_notes 工具获取备忘录的 entityId。参数说明:entityId 是备忘录的唯一标识符(从 search_notes 工具获取),text 是要追加的文本内容。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
15
+ description: "在指定备忘录中追加新内容。使用前必须先调用 search_notes 工具获取备忘录的 entityId。参数说明:entityId 是备忘录的唯一标识符(从 search_notes 工具获取),text 是要追加的文本内容。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。",
17
16
  parameters: {
18
17
  type: "object",
19
18
  properties: {
@@ -29,34 +28,19 @@ export const modifyNoteTool = {
29
28
  required: ["entityId", "text"],
30
29
  },
31
30
  async execute(toolCallId, params) {
32
- logger.log(`[MODIFY_NOTE_TOOL] 🚀 Starting execution`);
33
- logger.log(`[MODIFY_NOTE_TOOL] - toolCallId: ${toolCallId}`);
34
- logger.log(`[MODIFY_NOTE_TOOL] - params:`, JSON.stringify(params));
35
- logger.log(`[MODIFY_NOTE_TOOL] - timestamp: ${new Date().toISOString()}`);
36
31
  // Validate parameters
37
32
  if (!params.entityId || !params.text) {
38
- logger.error(`[MODIFY_NOTE_TOOL] ❌ Missing required parameters`);
39
33
  throw new Error("Missing required parameters: entityId and text are required");
40
34
  }
41
35
  // Get session context
42
- logger.log(`[MODIFY_NOTE_TOOL] 🔍 Attempting to get session context...`);
43
- const sessionContext = getLatestSessionContext();
36
+ const sessionContext = getCurrentSessionContext();
44
37
  if (!sessionContext) {
45
- logger.error(`[MODIFY_NOTE_TOOL] ❌ FAILED: No active session found!`);
46
- logger.error(`[MODIFY_NOTE_TOOL] - toolCallId: ${toolCallId}`);
47
38
  throw new Error("No active XY session found. Modify note tool can only be used during an active conversation.");
48
39
  }
49
- logger.log(`[MODIFY_NOTE_TOOL] ✅ Session context found`);
50
- logger.log(`[MODIFY_NOTE_TOOL] - sessionId: ${sessionContext.sessionId}`);
51
- logger.log(`[MODIFY_NOTE_TOOL] - taskId: ${sessionContext.taskId}`);
52
- logger.log(`[MODIFY_NOTE_TOOL] - messageId: ${sessionContext.messageId}`);
53
40
  const { config, sessionId, taskId, messageId } = sessionContext;
54
41
  // Get WebSocket manager
55
- logger.log(`[MODIFY_NOTE_TOOL] 🔌 Getting WebSocket manager...`);
56
42
  const wsManager = getXYWebSocketManager(config);
57
- logger.log(`[MODIFY_NOTE_TOOL] ✅ WebSocket manager obtained`);
58
43
  // Build ModifyNote command
59
- logger.log(`[MODIFY_NOTE_TOOL] 📦 Building ModifyNote command...`);
60
44
  const command = {
61
45
  header: {
62
46
  namespace: "Common",
@@ -92,56 +76,36 @@ export const modifyNoteTool = {
92
76
  pageControlRelated: false,
93
77
  },
94
78
  };
95
- logger.log(`[MODIFY_NOTE_TOOL] - entityId: ${params.entityId}`);
96
- logger.log(`[MODIFY_NOTE_TOOL] - contentType: 1 (append mode)`);
97
- logger.log(`[MODIFY_NOTE_TOOL] - text length: ${params.text.length} characters`);
98
79
  // Send command and wait for response (60 second timeout)
99
- logger.log(`[MODIFY_NOTE_TOOL] ⏳ Setting up promise to wait for note modification response...`);
100
- logger.log(`[MODIFY_NOTE_TOOL] - Timeout: 60 seconds`);
101
80
  return new Promise((resolve, reject) => {
102
81
  const timeout = setTimeout(() => {
103
- logger.error(`[MODIFY_NOTE_TOOL] ⏰ Timeout: No response received within 60 seconds`);
104
82
  wsManager.off("data-event", handler);
105
83
  reject(new Error("修改备忘录超时(60秒)"));
106
84
  }, 60000);
107
85
  // Listen for data events from WebSocket
108
86
  const handler = (event) => {
109
- logger.log(`[MODIFY_NOTE_TOOL] 📨 Received data event:`, JSON.stringify(event));
110
87
  if (event.intentName === "ModifyNote") {
111
- logger.log(`[MODIFY_NOTE_TOOL] 🎯 ModifyNote event received`);
112
- logger.log(`[MODIFY_NOTE_TOOL] - status: ${event.status}`);
113
88
  clearTimeout(timeout);
114
89
  wsManager.off("data-event", handler);
115
90
  if (event.status === "success" && event.outputs) {
116
- logger.log(`[MODIFY_NOTE_TOOL] Note modified successfully`);
117
- logger.log(`[MODIFY_NOTE_TOOL] - outputs:`, JSON.stringify(event.outputs));
118
- // Return the result directly as requested
119
- const result = event.outputs.result;
120
- logger.log(`[MODIFY_NOTE_TOOL] 📝 Note updated:`);
121
- logger.log(`[MODIFY_NOTE_TOOL] - entityId: ${result?.entityId}`);
122
- logger.log(`[MODIFY_NOTE_TOOL] - title: ${result?.title}`);
123
- logger.log(`[MODIFY_NOTE_TOOL] - modifiedDate: ${result?.modifiedDate}`);
91
+ // 成功,直接返回完整的 event.outputs JSON 字符串
124
92
  resolve({
125
93
  content: [
126
94
  {
127
95
  type: "text",
128
- text: JSON.stringify(result),
96
+ text: JSON.stringify(event.outputs),
129
97
  },
130
98
  ],
131
99
  });
132
100
  }
133
101
  else {
134
- logger.error(`[MODIFY_NOTE_TOOL] ❌ Note modification failed`);
135
- logger.error(`[MODIFY_NOTE_TOOL] - status: ${event.status}`);
136
102
  reject(new Error(`修改备忘录失败: ${event.status}`));
137
103
  }
138
104
  }
139
105
  };
140
106
  // Register event handler
141
- logger.log(`[MODIFY_NOTE_TOOL] 📡 Registering data-event handler on WebSocket manager`);
142
107
  wsManager.on("data-event", handler);
143
108
  // Send the command
144
- logger.log(`[MODIFY_NOTE_TOOL] 📤 Sending ModifyNote command...`);
145
109
  sendCommand({
146
110
  config,
147
111
  sessionId,
@@ -150,10 +114,8 @@ export const modifyNoteTool = {
150
114
  command,
151
115
  })
152
116
  .then(() => {
153
- logger.log(`[MODIFY_NOTE_TOOL] ✅ Command sent successfully, waiting for response...`);
154
117
  })
155
118
  .catch((error) => {
156
- logger.error(`[MODIFY_NOTE_TOOL] ❌ Failed to send command:`, error);
157
119
  clearTimeout(timeout);
158
120
  wsManager.off("data-event", handler);
159
121
  reject(error);
@@ -1,7 +1,18 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { sendCommand } from "../formatter.js";
3
- import { getLatestSessionContext } from "./session-manager.js";
4
- import { logger } from "../utils/logger.js";
3
+ import { getCurrentSessionContext } from "./session-manager.js";
4
+ /**
5
+ * Duck-typed ToolInputError: openclaw 按 .name 字段匹配,不用 instanceof。
6
+ * 抛出此错误会让 openclaw 返回 HTTP 400 而非 500,
7
+ * LLM 会将其识别为参数错误而非瞬时故障,不会触发重试。
8
+ */
9
+ class ToolInputError extends Error {
10
+ status = 400;
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "ToolInputError";
14
+ }
15
+ }
5
16
  /**
6
17
  * XY note tool - creates a note on user's device.
7
18
  * Requires title and content parameters.
@@ -9,29 +20,39 @@ import { logger } from "../utils/logger.js";
9
20
  export const noteTool = {
10
21
  name: "create_note",
11
22
  label: "Create Note",
12
- description: "在用户设备上创建备忘录。需要提供备忘录标题和内容。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
23
+ description: `在用户设备上创建备忘录。需要提供备忘录标题和内容。
24
+ 注意:
25
+ a. 操作超时时间为60秒,请勿重复调用此工具
26
+ b. 如果遇到各类调用失败场景,最多只能重试一次,不可以重复调用多次。
27
+ c. 调用工具前需认真检查调用参数是否满足工具要求
28
+
29
+ 回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。
30
+ `,
13
31
  parameters: {
14
32
  type: "object",
15
33
  properties: {
16
34
  title: {
17
35
  type: "string",
18
- description: "备忘录标题",
36
+ description: "备忘录标题,必填",
19
37
  },
20
38
  content: {
21
39
  type: "string",
22
- description: "备忘录内容",
40
+ description: "备忘录内容,必填",
23
41
  },
24
42
  },
25
43
  required: ["title", "content"],
26
44
  },
27
45
  async execute(toolCallId, params) {
28
- logger.debug("Executing note tool, toolCallId:", toolCallId);
29
- // Validate parameters
30
- if (!params.title || !params.content) {
31
- throw new Error("Missing required parameters: title and content are required");
46
+ // Validate parameters 抛 ToolInputError 而非普通 Error,
47
+ // openclaw 返回 400 而非 500,明确告知 LLM 这是参数错误,不应重试。
48
+ if (typeof params.title !== "string" || !params.title) {
49
+ throw new ToolInputError("缺少必填参数 title(备忘录标题)");
50
+ }
51
+ if (typeof params.content !== "string" || !params.content) {
52
+ throw new ToolInputError("缺少必填参数 content(备忘录内容)");
32
53
  }
33
54
  // Get session context
34
- const sessionContext = getLatestSessionContext();
55
+ const sessionContext = getCurrentSessionContext();
35
56
  if (!sessionContext) {
36
57
  throw new Error("No active XY session found. Note tool can only be used during an active conversation.");
37
58
  }
@@ -80,28 +101,16 @@ export const noteTool = {
80
101
  }, 60000);
81
102
  // Listen for data events from WebSocket
82
103
  const handler = (event) => {
83
- logger.debug("Received data event:", event);
84
104
  if (event.intentName === "CreateNote") {
85
105
  clearTimeout(timeout);
86
106
  wsManager.off("data-event", handler);
87
107
  if (event.status === "success" && event.outputs) {
88
- const { result, code } = event.outputs;
89
- logger.log(`Note created: title=${result?.title}, id=${result?.entityId}`);
108
+ // 成功,直接返回完整的 event.outputs JSON 字符串
90
109
  resolve({
91
110
  content: [
92
111
  {
93
112
  type: "text",
94
- text: JSON.stringify({
95
- success: true,
96
- note: {
97
- entityId: result?.entityId,
98
- title: result?.title,
99
- content: result?.content,
100
- entityName: result?.entityName,
101
- modifiedDate: result?.modifiedDate,
102
- },
103
- code,
104
- }),
113
+ text: JSON.stringify(event.outputs),
105
114
  },
106
115
  ],
107
116
  });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * XY search alarm tool - searches alarms on user's device.
3
+ * Returns matching alarms based on various filter criteria.
4
+ *
5
+ * At least one search criterion must be provided.
6
+ * Multiple criteria can be combined.
7
+ */
8
+ export declare const searchAlarmTool: any;