@ynhcj/xiaoyi-channel 0.0.87-beta → 0.0.87-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 (131) hide show
  1. package/dist/index.d.ts +6 -9
  2. package/dist/index.js +26 -21
  3. package/dist/src/bot.js +26 -3
  4. package/dist/src/channel.js +2 -30
  5. package/dist/src/client.js +7 -1
  6. package/dist/src/login-token-handler.d.ts +8 -0
  7. package/dist/src/login-token-handler.js +60 -0
  8. package/dist/src/message-queue.d.ts +17 -0
  9. package/dist/src/message-queue.js +51 -0
  10. package/dist/src/monitor.js +64 -4
  11. package/dist/src/provider.d.ts +2 -1
  12. package/dist/src/provider.js +440 -31
  13. package/dist/src/reply-dispatcher.js +6 -0
  14. package/dist/src/runtime.d.ts +3 -11
  15. package/dist/src/runtime.js +6 -18
  16. package/dist/src/self-evolution-handler.d.ts +7 -0
  17. package/dist/src/self-evolution-handler.js +140 -0
  18. package/dist/src/self-evolution-keyword.d.ts +9 -0
  19. package/dist/src/self-evolution-keyword.js +147 -0
  20. package/dist/src/self-evolution-tool-result-nudge.d.ts +3 -0
  21. package/dist/src/self-evolution-tool-result-nudge.js +96 -0
  22. package/dist/src/skill-retriever/config.d.ts +4 -0
  23. package/dist/src/skill-retriever/config.js +23 -0
  24. package/dist/src/skill-retriever/hooks.d.ts +22 -0
  25. package/dist/src/skill-retriever/hooks.js +82 -0
  26. package/dist/src/skill-retriever/tool-search.d.ts +16 -0
  27. package/dist/src/skill-retriever/tool-search.js +172 -0
  28. package/dist/src/skill-retriever/types.d.ts +36 -0
  29. package/dist/src/skill-retriever/types.js +1 -0
  30. package/dist/src/task-manager.d.ts +4 -0
  31. package/dist/src/task-manager.js +12 -1
  32. package/dist/src/tools/calendar-tool.d.ts +2 -1
  33. package/dist/src/tools/calendar-tool.js +112 -116
  34. package/dist/src/tools/call-device-tool.d.ts +6 -0
  35. package/dist/src/tools/call-device-tool.js +153 -0
  36. package/dist/src/tools/call-phone-tool.d.ts +2 -1
  37. package/dist/src/tools/call-phone-tool.js +109 -113
  38. package/dist/src/tools/create-alarm-tool.d.ts +2 -1
  39. package/dist/src/tools/create-alarm-tool.js +227 -231
  40. package/dist/src/tools/create-all-tools.d.ts +16 -0
  41. package/dist/src/tools/create-all-tools.js +50 -0
  42. package/dist/src/tools/delete-alarm-tool.d.ts +2 -1
  43. package/dist/src/tools/delete-alarm-tool.js +131 -135
  44. package/dist/src/tools/device-tool-map.js +2 -0
  45. package/dist/src/tools/get-alarm-tool-schema.d.ts +17 -0
  46. package/dist/src/tools/get-alarm-tool-schema.js +17 -0
  47. package/dist/src/tools/get-calendar-tool-schema.d.ts +17 -0
  48. package/dist/src/tools/get-calendar-tool-schema.js +13 -0
  49. package/dist/src/tools/get-collection-tool-schema.d.ts +17 -0
  50. package/dist/src/tools/get-collection-tool-schema.js +12 -0
  51. package/dist/src/tools/get-contact-tool-schema.d.ts +17 -0
  52. package/dist/src/tools/get-contact-tool-schema.js +17 -0
  53. package/dist/src/tools/get-device-file-tool-schema.d.ts +17 -0
  54. package/dist/src/tools/get-device-file-tool-schema.js +14 -0
  55. package/dist/src/tools/get-email-tool-schema.d.ts +17 -0
  56. package/dist/src/tools/get-email-tool-schema.js +12 -0
  57. package/dist/src/tools/get-note-tool-schema.d.ts +17 -0
  58. package/dist/src/tools/get-note-tool-schema.js +15 -0
  59. package/dist/src/tools/get-photo-tool-schema.d.ts +17 -0
  60. package/dist/src/tools/get-photo-tool-schema.js +13 -0
  61. package/dist/src/tools/image-reading-tool.d.ts +2 -1
  62. package/dist/src/tools/image-reading-tool.js +86 -90
  63. package/dist/src/tools/location-tool.d.ts +2 -1
  64. package/dist/src/tools/location-tool.js +87 -91
  65. package/dist/src/tools/login-token-tool.d.ts +6 -0
  66. package/dist/src/tools/login-token-tool.js +133 -0
  67. package/dist/src/tools/modify-alarm-tool.d.ts +2 -1
  68. package/dist/src/tools/modify-alarm-tool.js +232 -236
  69. package/dist/src/tools/modify-note-tool.d.ts +2 -1
  70. package/dist/src/tools/modify-note-tool.js +104 -108
  71. package/dist/src/tools/note-tool.d.ts +2 -1
  72. package/dist/src/tools/note-tool.js +103 -107
  73. package/dist/src/tools/query-app-message-tool.d.ts +5 -0
  74. package/dist/src/tools/query-app-message-tool.js +135 -0
  75. package/dist/src/tools/query-memory-data-tool.d.ts +5 -0
  76. package/dist/src/tools/query-memory-data-tool.js +151 -0
  77. package/dist/src/tools/query-todo-task-tool.d.ts +5 -0
  78. package/dist/src/tools/query-todo-task-tool.js +130 -0
  79. package/dist/src/tools/save-file-to-phone-tool.d.ts +2 -1
  80. package/dist/src/tools/save-file-to-phone-tool.js +127 -131
  81. package/dist/src/tools/save-media-to-gallery-tool.d.ts +2 -1
  82. package/dist/src/tools/save-media-to-gallery-tool.js +134 -138
  83. package/dist/src/tools/save-self-evolution-skill-tool.d.ts +2 -0
  84. package/dist/src/tools/save-self-evolution-skill-tool.js +410 -0
  85. package/dist/src/tools/schema-tool-factory.d.ts +27 -0
  86. package/dist/src/tools/schema-tool-factory.js +32 -0
  87. package/dist/src/tools/search-alarm-tool.d.ts +2 -1
  88. package/dist/src/tools/search-alarm-tool.js +171 -175
  89. package/dist/src/tools/search-calendar-tool.d.ts +2 -1
  90. package/dist/src/tools/search-calendar-tool.js +145 -149
  91. package/dist/src/tools/search-contact-tool.d.ts +2 -1
  92. package/dist/src/tools/search-contact-tool.js +98 -102
  93. package/dist/src/tools/search-email-tool.d.ts +2 -1
  94. package/dist/src/tools/search-email-tool.js +107 -111
  95. package/dist/src/tools/search-file-tool.d.ts +2 -1
  96. package/dist/src/tools/search-file-tool.js +99 -103
  97. package/dist/src/tools/search-message-tool.d.ts +2 -1
  98. package/dist/src/tools/search-message-tool.js +100 -104
  99. package/dist/src/tools/search-note-tool.d.ts +2 -1
  100. package/dist/src/tools/search-note-tool.js +95 -99
  101. package/dist/src/tools/search-photo-gallery-tool.d.ts +2 -1
  102. package/dist/src/tools/search-photo-gallery-tool.js +34 -38
  103. package/dist/src/tools/send-email-tool.d.ts +5 -0
  104. package/dist/src/tools/send-email-tool.js +131 -0
  105. package/dist/src/tools/send-file-to-user-tool.d.ts +2 -1
  106. package/dist/src/tools/send-file-to-user-tool.js +153 -155
  107. package/dist/src/tools/send-message-tool.d.ts +2 -1
  108. package/dist/src/tools/send-message-tool.js +119 -123
  109. package/dist/src/tools/session-manager.d.ts +21 -6
  110. package/dist/src/tools/session-manager.js +135 -18
  111. package/dist/src/tools/upload-file-tool.d.ts +2 -1
  112. package/dist/src/tools/upload-file-tool.js +79 -83
  113. package/dist/src/tools/upload-photo-tool.d.ts +2 -1
  114. package/dist/src/tools/upload-photo-tool.js +70 -74
  115. package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +2 -1
  116. package/dist/src/tools/xiaoyi-add-collection-tool.js +143 -147
  117. package/dist/src/tools/xiaoyi-collection-tool.d.ts +2 -1
  118. package/dist/src/tools/xiaoyi-collection-tool.js +112 -116
  119. package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +2 -1
  120. package/dist/src/tools/xiaoyi-delete-collection-tool.js +124 -128
  121. package/dist/src/tools/xiaoyi-gui-tool.d.ts +2 -1
  122. package/dist/src/tools/xiaoyi-gui-tool.js +84 -88
  123. package/dist/src/utils/runtime-manager.js +24 -2
  124. package/dist/src/utils/self-evolution-manager.d.ts +10 -0
  125. package/dist/src/utils/self-evolution-manager.js +68 -0
  126. package/dist/src/utils/tool-call-nudge-manager.d.ts +16 -0
  127. package/dist/src/utils/tool-call-nudge-manager.js +47 -0
  128. package/dist/src/websocket.d.ts +3 -0
  129. package/dist/src/websocket.js +94 -24
  130. package/openclaw.plugin.json +21 -0
  131. package/package.json +3 -3
@@ -1,6 +1,5 @@
1
1
  // Image Reading tool implementation
2
2
  import { XYFileUploadService } from "../file-upload.js";
3
- import { getCurrentSessionContext } from "./session-manager.js";
4
3
  import fetch from "node-fetch";
5
4
  import fs from "fs/promises";
6
5
  import path from "path";
@@ -201,10 +200,12 @@ async function callImageUnderstandingAPI(imageUrl, text, apiKey, uid, fileUpload
201
200
  * XY Image Reading tool - performs image understanding using local or remote image URLs.
202
201
  * Supports both local file paths and remote URLs.
203
202
  */
204
- export const imageReadingTool = {
205
- name: "image_reading",
206
- label: "Image Reading",
207
- description: `
203
+ export function createImageReadingTool(ctx) {
204
+ const { config, sessionId, taskId, messageId } = ctx;
205
+ return {
206
+ name: "image_reading",
207
+ label: "Image Reading",
208
+ description: `
208
209
  工具使用场景:
209
210
  【必须调用此工具的情况】
210
211
  1. 用户消息中包含 mediaPath 字段且不为空(表示用户发送了图片)
@@ -230,96 +231,91 @@ a. 支持常见图片格式(jpg, png, gif等)
230
231
  b. 远程图片会先下载到本地再处理
231
232
  c. 操作超时时间为2分钟(120秒)
232
233
  d. 返回图像理解的文本描述内容`,
233
- parameters: {
234
- type: "object",
235
- properties: {
236
- localUrl: {
237
- type: "string",
238
- description: "本地图片文件路径(可选,通常从用户消息的 mediaPath 字段获取)",
239
- },
240
- remoteUrl: {
241
- type: "string",
242
- description: "公网图片地址(可选),公网图片地址(HTTP/HTTPS URL",
243
- },
244
- prompt: {
245
- type: "string",
246
- description: "对图片的提示问题,默认为'描述这张图片内容',可根据用户的具体问题自定义",
234
+ parameters: {
235
+ type: "object",
236
+ properties: {
237
+ localUrl: {
238
+ type: "string",
239
+ description: "本地图片文件路径(可选,通常从用户消息的 mediaPath 字段获取)",
240
+ },
241
+ remoteUrl: {
242
+ type: "string",
243
+ description: "公网图片地址(可选),公网图片地址(HTTP/HTTPS URL),注意不要对原始url做任何截断(例如裁减掉链接后面的鉴权信息或者修改域名后缀),必须使用上下文中完整的图片地址",
244
+ },
245
+ prompt: {
246
+ type: "string",
247
+ description: "对图片的提示问题,默认为'描述这张图片内容',可根据用户的具体问题自定义",
248
+ },
247
249
  },
248
250
  },
249
- },
250
- async execute(toolCallId, params) {
251
- // Validate that at least one parameter is provided
252
- if (!params.localUrl && !params.remoteUrl) {
253
- throw new Error("At least one of localUrl or remoteUrl must be provided");
254
- }
255
- // Get prompt (default to "描述这张图片内容")
256
- const prompt = params.prompt || "描述这张图片内容";
257
- // Get session context
258
- const sessionContext = getCurrentSessionContext();
259
- if (!sessionContext) {
260
- throw new Error("No active XY session found. Image reading tool can only be used during an active conversation.");
261
- }
262
- const { config } = sessionContext;
263
- // Create upload service
264
- const uploadService = new XYFileUploadService(config.fileUploadUrl, config.apiKey, config.uid);
265
- let processedImage = null;
266
- let downloadedFile = null;
267
- try {
268
- // Process image input (prefer localUrl over remoteUrl)
269
- const imageInput = params.localUrl || params.remoteUrl;
270
- processedImage = await processImageInput(imageInput, uploadService);
271
- // Track downloaded file for cleanup
272
- if (processedImage.localPath) {
273
- downloadedFile = processedImage.localPath;
251
+ async execute(toolCallId, params) {
252
+ // Validate that at least one parameter is provided
253
+ if (!params.localUrl && !params.remoteUrl) {
254
+ throw new Error("At least one of localUrl or remoteUrl must be provided");
274
255
  }
275
- // Call image understanding API
276
- const caption = await callImageUnderstandingAPI(processedImage.imageUrl, prompt, config.apiKey, config.uid, config.fileUploadUrl);
277
- // Clean up downloaded file if any
278
- if (downloadedFile) {
279
- try {
280
- await fs.unlink(downloadedFile);
256
+ // Get prompt (default to "描述这张图片内容")
257
+ const prompt = params.prompt || "描述这张图片内容";
258
+ // Create upload service
259
+ const uploadService = new XYFileUploadService(config.fileUploadUrl, config.apiKey, config.uid);
260
+ let processedImage = null;
261
+ let downloadedFile = null;
262
+ try {
263
+ // Process image input (prefer localUrl over remoteUrl)
264
+ const imageInput = params.localUrl || params.remoteUrl;
265
+ processedImage = await processImageInput(imageInput, uploadService);
266
+ // Track downloaded file for cleanup
267
+ if (processedImage.localPath) {
268
+ downloadedFile = processedImage.localPath;
281
269
  }
282
- catch (error) {
270
+ // Call image understanding API
271
+ const caption = await callImageUnderstandingAPI(processedImage.imageUrl, prompt, config.apiKey, config.uid, config.fileUploadUrl);
272
+ // Clean up downloaded file if any
273
+ if (downloadedFile) {
274
+ try {
275
+ await fs.unlink(downloadedFile);
276
+ }
277
+ catch (error) {
278
+ }
283
279
  }
280
+ return {
281
+ content: [
282
+ {
283
+ type: "text",
284
+ text: JSON.stringify({
285
+ caption,
286
+ prompt,
287
+ imageSource: params.localUrl ? "local" : "remote",
288
+ success: true,
289
+ }),
290
+ },
291
+ ],
292
+ };
284
293
  }
285
- return {
286
- content: [
287
- {
288
- type: "text",
289
- text: JSON.stringify({
290
- caption,
291
- prompt,
292
- imageSource: params.localUrl ? "local" : "remote",
293
- success: true,
294
- }),
295
- },
296
- ],
297
- };
298
- }
299
- catch (error) {
300
- // Clean up downloaded file on error
301
- if (downloadedFile) {
302
- try {
303
- await fs.unlink(downloadedFile);
304
- }
305
- catch (cleanupError) {
294
+ catch (error) {
295
+ // Clean up downloaded file on error
296
+ if (downloadedFile) {
297
+ try {
298
+ await fs.unlink(downloadedFile);
299
+ }
300
+ catch (cleanupError) {
301
+ }
306
302
  }
303
+ const errorMessage = error instanceof Error ? error.message : "图片分析失败";
304
+ // Return error result instead of throwing
305
+ return {
306
+ content: [
307
+ {
308
+ type: "text",
309
+ text: JSON.stringify({
310
+ error: errorMessage,
311
+ prompt,
312
+ imageSource: params.localUrl ? "local" : "remote",
313
+ success: false,
314
+ }),
315
+ },
316
+ ],
317
+ };
307
318
  }
308
- const errorMessage = error instanceof Error ? error.message : "图片分析失败";
309
- // Return error result instead of throwing
310
- return {
311
- content: [
312
- {
313
- type: "text",
314
- text: JSON.stringify({
315
- error: errorMessage,
316
- prompt,
317
- imageSource: params.localUrl ? "local" : "remote",
318
- success: false,
319
- }),
320
- },
321
- ],
322
- };
323
- }
324
- },
325
- };
319
+ },
320
+ };
321
+ }
@@ -1,5 +1,6 @@
1
+ import type { SessionContext } from "./session-manager.js";
1
2
  /**
2
3
  * XY location tool - gets user's current location.
3
4
  * Returns WGS84 coordinates (latitude, longitude).
4
5
  */
5
- export declare const locationTool: any;
6
+ export declare function createLocationTool(ctx: SessionContext): any;
@@ -1,102 +1,98 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { sendCommand } from "../formatter.js";
3
- import { getCurrentSessionContext } from "./session-manager.js";
4
3
  /**
5
4
  * XY location tool - gets user's current location.
6
5
  * Returns WGS84 coordinates (latitude, longitude).
7
6
  */
8
- export const locationTool = {
9
- name: "get_user_location",
10
- label: "Get User Location",
11
- description: "获取用户当前位置(经纬度坐标,WGS84坐标系)。需要用户设备授权位置访问权限。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。",
12
- parameters: {
13
- type: "object",
14
- properties: {},
15
- required: [],
16
- },
17
- async execute(toolCallId, params) {
18
- // Get session context
19
- const sessionContext = getCurrentSessionContext();
20
- if (!sessionContext) {
21
- throw new Error("No active XY session found. Location tool can only be used during an active conversation.");
22
- }
23
- const { config, sessionId, taskId, messageId } = sessionContext;
24
- // Get WebSocket manager
25
- const wsManager = getXYWebSocketManager(config);
26
- // Build GetCurrentLocation command
27
- const command = {
28
- header: {
29
- namespace: "Common",
30
- name: "Action",
31
- },
32
- payload: {
33
- cardParam: {},
34
- executeParam: {
35
- achieveType: "INTENT",
36
- actionResponse: true,
37
- bundleName: "com.huawei.hmos.aidispatchservice",
38
- dimension: "",
39
- executeMode: "background",
40
- intentName: "GetCurrentLocation",
41
- intentParam: {
42
- isNeedGeoAddress: true,
7
+ export function createLocationTool(ctx) {
8
+ const { config, sessionId, taskId, messageId } = ctx;
9
+ return {
10
+ name: "get_user_location",
11
+ label: "Get User Location",
12
+ description: "获取用户当前位置(经纬度坐标,WGS84坐标系)。需要用户设备授权位置访问权限。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。",
13
+ parameters: {
14
+ type: "object",
15
+ properties: {},
16
+ required: [],
17
+ },
18
+ async execute(toolCallId, params) {
19
+ // Get WebSocket manager
20
+ const wsManager = getXYWebSocketManager(config);
21
+ // Build GetCurrentLocation command
22
+ const command = {
23
+ header: {
24
+ namespace: "Common",
25
+ name: "Action",
26
+ },
27
+ payload: {
28
+ cardParam: {},
29
+ executeParam: {
30
+ achieveType: "INTENT",
31
+ actionResponse: true,
32
+ bundleName: "com.huawei.hmos.aidispatchservice",
33
+ dimension: "",
34
+ executeMode: "background",
35
+ intentName: "GetCurrentLocation",
36
+ intentParam: {
37
+ isNeedGeoAddress: true,
38
+ },
39
+ needUnlock: true,
40
+ permissionId: [],
41
+ timeOut: 5,
43
42
  },
44
- needUnlock: true,
45
- permissionId: [],
46
- timeOut: 5,
43
+ needUploadResult: true,
44
+ pageControlRelated: false,
45
+ responses: [{
46
+ displayText: "",
47
+ resultCode: "",
48
+ ttsText: "",
49
+ }],
47
50
  },
48
- needUploadResult: true,
49
- pageControlRelated: false,
50
- responses: [{
51
- displayText: "",
52
- resultCode: "",
53
- ttsText: "",
54
- }],
55
- },
56
- };
57
- // Send command and wait for response (60 second timeout)
58
- return new Promise((resolve, reject) => {
59
- const timeout = setTimeout(() => {
60
- wsManager.off("data-event", handler);
61
- reject(new Error("获取位置超时(60秒)"));
62
- }, 60000);
63
- // Listen for data events from WebSocket
64
- const handler = (event) => {
65
- if (event.intentName === "GetCurrentLocation") {
66
- clearTimeout(timeout);
51
+ };
52
+ // Send command and wait for response (60 second timeout)
53
+ return new Promise((resolve, reject) => {
54
+ const timeout = setTimeout(() => {
67
55
  wsManager.off("data-event", handler);
68
- if (event.status === "success" && event.outputs) {
69
- // 成功,直接返回完整的 event.outputs JSON 字符串
70
- resolve({
71
- content: [
72
- {
73
- type: "text",
74
- text: JSON.stringify(event.outputs),
75
- }
76
- ]
77
- });
78
- }
79
- else {
80
- reject(new Error(`获取位置失败: ${event.status}`));
56
+ reject(new Error("获取位置超时(60秒)"));
57
+ }, 60000);
58
+ // Listen for data events from WebSocket
59
+ const handler = (event) => {
60
+ if (event.intentName === "GetCurrentLocation") {
61
+ clearTimeout(timeout);
62
+ wsManager.off("data-event", handler);
63
+ if (event.status === "success" && event.outputs) {
64
+ // 成功,直接返回完整的 event.outputs JSON 字符串
65
+ resolve({
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: JSON.stringify(event.outputs),
70
+ }
71
+ ]
72
+ });
73
+ }
74
+ else {
75
+ reject(new Error(`获取位置失败: ${event.status}`));
76
+ }
81
77
  }
82
- }
83
- };
84
- // Register event handler
85
- // Note: The WebSocket manager needs to emit 'data-event' when receiving data events
86
- wsManager.on("data-event", handler);
87
- // Send the command
88
- sendCommand({
89
- config,
90
- sessionId,
91
- taskId,
92
- messageId,
93
- command,
94
- }).then(() => {
95
- }).catch((error) => {
96
- clearTimeout(timeout);
97
- wsManager.off("data-event", handler);
98
- reject(error);
78
+ };
79
+ // Register event handler
80
+ // Note: The WebSocket manager needs to emit 'data-event' when receiving data events
81
+ wsManager.on("data-event", handler);
82
+ // Send the command
83
+ sendCommand({
84
+ config,
85
+ sessionId,
86
+ taskId,
87
+ messageId,
88
+ command,
89
+ }).then(() => {
90
+ }).catch((error) => {
91
+ clearTimeout(timeout);
92
+ wsManager.off("data-event", handler);
93
+ reject(error);
94
+ });
99
95
  });
100
- });
101
- },
102
- };
96
+ },
97
+ };
98
+ }
@@ -0,0 +1,6 @@
1
+ import type { SessionContext } from "./session-manager.js";
2
+ /**
3
+ * huawei_id_tool 工具
4
+ * 当 skill 依赖用户获取鉴权信息时,此工具协助用户快速获取鉴权信息。
5
+ */
6
+ export declare function createLoginTokenTool(ctx: SessionContext): any;
@@ -0,0 +1,133 @@
1
+ // Login Token tool - 自动获取用户授权信息
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import { getXYWebSocketManager } from "../client.js";
4
+ import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
5
+ import { readFileSync, existsSync } from "fs";
6
+ import { logger } from "../utils/logger.js";
7
+ const TOKEN_FILE_PATH = "/home/sandbox/.openclaw/.xiaoyitoken.json";
8
+ const POLL_INTERVAL_MS = 5000; // 5 seconds
9
+ const TIMEOUT_MS = 60000; // 1 minute
10
+ const TOKEN_VALIDITY_MS = 5 * 60 * 1000; // 5 minutes
11
+ /**
12
+ * huawei_id_tool 工具
13
+ * 当 skill 依赖用户获取鉴权信息时,此工具协助用户快速获取鉴权信息。
14
+ */
15
+ export function createLoginTokenTool(ctx) {
16
+ const { config, sessionId, taskId, messageId } = ctx;
17
+ return {
18
+ name: "huawei_id_tool",
19
+ label: "Get Login Token",
20
+ description: "获取用户授权信息。当skill需要用户鉴权时调用此工具,工具会向用户端发送授权请求,等待用户完成授权后返回结果。请勿重复调用此工具。",
21
+ parameters: {
22
+ type: "object",
23
+ properties: {
24
+ clientId: {
25
+ type: "string",
26
+ description: "账号服务唯一标识,在执行具体skill过程中会提供",
27
+ },
28
+ skillName: {
29
+ type: "string",
30
+ description: "具体skill的名称",
31
+ },
32
+ },
33
+ required: ["clientId", "skillName"],
34
+ },
35
+ async execute(toolCallId, params) {
36
+ const { clientId, skillName } = params;
37
+ if (!clientId || typeof clientId !== "string" || clientId.trim() === "") {
38
+ throw new Error("Missing required parameter: clientId must be a non-empty string");
39
+ }
40
+ if (!skillName || typeof skillName !== "string" || skillName.trim() === "") {
41
+ throw new Error("Missing required parameter: skillName must be a non-empty string");
42
+ }
43
+ const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
44
+ const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
45
+ // (1) Build and send getLoginToken artifact
46
+ const artifactId = uuidv4();
47
+ const artifact = {
48
+ taskId: currentTaskId,
49
+ kind: "artifact-update",
50
+ append: false,
51
+ lastChunk: true,
52
+ final: false,
53
+ artifact: {
54
+ artifactId,
55
+ parts: [
56
+ {
57
+ kind: "getLoginToken",
58
+ clientId: clientId.trim(),
59
+ skillName: skillName.trim(),
60
+ },
61
+ ],
62
+ },
63
+ };
64
+ const jsonRpcResponse = {
65
+ jsonrpc: "2.0",
66
+ id: currentMessageId,
67
+ result: artifact,
68
+ };
69
+ const wsManager = getXYWebSocketManager(config);
70
+ const outboundMessage = {
71
+ msgType: "agent_response",
72
+ agentId: config.agentId,
73
+ sessionId,
74
+ taskId: currentTaskId,
75
+ msgDetail: JSON.stringify(jsonRpcResponse),
76
+ };
77
+ logger.log(`[LOGIN_TOKEN] Sending getLoginToken artifact for clientId=${clientId}, skillName=${skillName}`);
78
+ await wsManager.sendMessage(sessionId, outboundMessage);
79
+ logger.log(`[LOGIN_TOKEN] Artifact sent successfully`);
80
+ // (2) Poll .xiaoyitoken.json every 5 seconds
81
+ const startTime = Date.now();
82
+ return new Promise((resolve) => {
83
+ const poll = () => {
84
+ const elapsed = Date.now() - startTime;
85
+ if (elapsed >= TIMEOUT_MS) {
86
+ // (4) Timeout after 1 minute
87
+ logger.log(`[LOGIN_TOKEN] Timeout: failed to get login token for clientId=${clientId}`);
88
+ resolve({
89
+ content: [
90
+ {
91
+ type: "text",
92
+ text: "获取用户授权失败",
93
+ },
94
+ ],
95
+ });
96
+ return;
97
+ }
98
+ try {
99
+ if (existsSync(TOKEN_FILE_PATH)) {
100
+ const content = readFileSync(TOKEN_FILE_PATH, "utf-8");
101
+ const tokens = JSON.parse(content);
102
+ const match = tokens.find((t) => t.clientId === clientId.trim());
103
+ if (match) {
104
+ const tokenTime = Number(match.timestamp);
105
+ const diff = Date.now() - tokenTime;
106
+ if (diff <= TOKEN_VALIDITY_MS) {
107
+ // (3) Found valid token
108
+ logger.log(`[LOGIN_TOKEN] Successfully got login token for clientId=${clientId}`);
109
+ resolve({
110
+ content: [
111
+ {
112
+ type: "text",
113
+ text: "获取用户授权成功",
114
+ },
115
+ ],
116
+ });
117
+ return;
118
+ }
119
+ }
120
+ }
121
+ }
122
+ catch (err) {
123
+ logger.log(`[LOGIN_TOKEN] Error reading token file: ${err}`);
124
+ }
125
+ // Not found or not valid, poll again after 5 seconds
126
+ setTimeout(poll, POLL_INTERVAL_MS);
127
+ };
128
+ // Start polling after 5 seconds
129
+ setTimeout(poll, POLL_INTERVAL_MS);
130
+ });
131
+ },
132
+ };
133
+ }
@@ -1,3 +1,4 @@
1
+ import type { SessionContext } from "./session-manager.js";
1
2
  /**
2
3
  * XY modify alarm tool - modifies an existing alarm on user's device.
3
4
  * Requires entityId from search_alarm or create_alarm tool.
@@ -6,4 +7,4 @@
6
7
  * 1. Call search_alarm or create_alarm tool first to get entityId
7
8
  * 2. Use the entityId to identify which alarm to modify
8
9
  */
9
- export declare const modifyAlarmTool: any;
10
+ export declare function createModifyAlarmTool(ctx: SessionContext): any;