@ynhcj/xiaoyi-channel 0.0.151-beta → 0.0.151-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 (76) hide show
  1. package/dist/index.js +23 -0
  2. package/dist/src/bot.js +9 -1
  3. package/dist/src/channel.js +59 -5
  4. package/dist/src/cron-command.d.ts +15 -0
  5. package/dist/src/cron-command.js +49 -0
  6. package/dist/src/cron-query-handler.d.ts +7 -0
  7. package/dist/src/cron-query-handler.js +188 -0
  8. package/dist/src/cspl/call_api.d.ts +1 -1
  9. package/dist/src/cspl/call_api.js +2 -2
  10. package/dist/src/cspl/config.js +30 -10
  11. package/dist/src/cspl/constants.d.ts +3 -0
  12. package/dist/src/cspl/constants.js +5 -0
  13. package/dist/src/cspl/sentinel_hook.js +17 -3
  14. package/dist/src/cspl/utils.js +2 -2
  15. package/dist/src/formatter.d.ts +14 -1
  16. package/dist/src/formatter.js +31 -8
  17. package/dist/src/monitor.js +13 -1
  18. package/dist/src/parser.d.ts +2 -1
  19. package/dist/src/parser.js +55 -0
  20. package/dist/src/provider.js +19 -17
  21. package/dist/src/push.d.ts +11 -1
  22. package/dist/src/push.js +93 -2
  23. package/dist/src/reply-dispatcher.js +113 -14
  24. package/dist/src/self-evolution-handler.js +1 -1
  25. package/dist/src/tools/agent-as-skill-tool.js +56 -4
  26. package/dist/src/tools/calendar-tool.js +2 -1
  27. package/dist/src/tools/call-device-tool.js +0 -3
  28. package/dist/src/tools/call-phone-tool.js +2 -1
  29. package/dist/src/tools/create-alarm-tool.js +2 -1
  30. package/dist/src/tools/create-all-tools.js +8 -4
  31. package/dist/src/tools/delete-alarm-tool.js +2 -1
  32. package/dist/src/tools/device-tool-map.d.ts +1 -1
  33. package/dist/src/tools/device-tool-map.js +12 -5
  34. package/dist/src/tools/discover-cross-devices-tool.d.ts +2 -0
  35. package/dist/src/tools/discover-cross-devices-tool.js +235 -0
  36. package/dist/src/tools/display-a2ui-card-tool.d.ts +2 -0
  37. package/dist/src/tools/display-a2ui-card-tool.js +85 -0
  38. package/dist/src/tools/find-pc-devices-tool.js +1 -0
  39. package/dist/src/tools/get-collection-tool-schema.js +1 -1
  40. package/dist/src/tools/get-device-file-tool-schema.js +2 -3
  41. package/dist/src/tools/location-tool.js +2 -1
  42. package/dist/src/tools/modify-alarm-tool.js +2 -1
  43. package/dist/src/tools/modify-note-tool.js +2 -1
  44. package/dist/src/tools/note-tool.js +2 -1
  45. package/dist/src/tools/query-app-message-tool.js +4 -3
  46. package/dist/src/tools/query-memory-data-tool.js +4 -3
  47. package/dist/src/tools/query-todo-task-tool.js +4 -3
  48. package/dist/src/tools/save-file-to-phone-tool.js +2 -1
  49. package/dist/src/tools/save-media-to-gallery-tool.js +2 -1
  50. package/dist/src/tools/schema-tool-factory.js +1 -1
  51. package/dist/src/tools/search-alarm-tool.js +2 -1
  52. package/dist/src/tools/search-calendar-tool.js +2 -1
  53. package/dist/src/tools/search-contact-tool.js +2 -1
  54. package/dist/src/tools/search-email-tool.js +4 -3
  55. package/dist/src/tools/search-file-tool.js +6 -10
  56. package/dist/src/tools/search-message-tool.js +1 -0
  57. package/dist/src/tools/search-note-tool.js +2 -1
  58. package/dist/src/tools/search-photo-gallery-tool.js +4 -3
  59. package/dist/src/tools/send-cross-device-task-tool.d.ts +2 -0
  60. package/dist/src/tools/send-cross-device-task-tool.js +299 -0
  61. package/dist/src/tools/send-email-tool.js +4 -3
  62. package/dist/src/tools/send-file-to-user-tool.d.ts +1 -1
  63. package/dist/src/tools/send-file-to-user-tool.js +35 -6
  64. package/dist/src/tools/send-message-tool.js +1 -0
  65. package/dist/src/tools/session-manager.d.ts +14 -1
  66. package/dist/src/tools/session-manager.js +73 -0
  67. package/dist/src/tools/upload-file-tool.js +6 -14
  68. package/dist/src/tools/upload-photo-tool.js +4 -3
  69. package/dist/src/tools/xiaoyi-add-collection-tool.js +4 -2
  70. package/dist/src/tools/xiaoyi-collection-tool.js +3 -2
  71. package/dist/src/tools/xiaoyi-delete-collection-tool.js +3 -2
  72. package/dist/src/tools/xiaoyi-gui-tool.js +6 -0
  73. package/dist/src/types.d.ts +22 -0
  74. package/dist/src/websocket.d.ts +3 -0
  75. package/dist/src/websocket.js +207 -15
  76. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { getXYWebSocketManager } from "../client.js";
3
3
  import { sendCommand } from "../formatter.js";
4
4
  import { getCurrentTaskId } from "../task-manager.js";
5
5
  import { logger } from "../utils/logger.js";
6
+ import { XYFileUploadService } from "../file-upload.js";
6
7
  /**
7
8
  * Agent-as-skill tool - invokes a registered agent by agentId as a skill.
8
9
  * The tool receives the agentId, query, and optional file attachments,
@@ -11,7 +12,7 @@ import { logger } from "../utils/logger.js";
11
12
  export function createAgentAsSkillTool(ctx) {
12
13
  const { config, sessionId, taskId, messageId } = ctx;
13
14
  return {
14
- name: "agent-as-skill-tool",
15
+ name: "agent_as_a_tool",
15
16
  label: "Agent as Skill Tool",
16
17
  description: `智能体作为skill的执行元工具。当需要调用其他已注册的Agent来执行特定任务时使用此工具。
17
18
  该工具会将用户请求和可选的附件文件转发给目标Agent执行,并返回执行结果。
@@ -37,8 +38,7 @@ export function createAgentAsSkillTool(ctx) {
37
38
  description: "用户原始请求文本,原样转发给目标Agent执行",
38
39
  },
39
40
  filesInfo: {
40
- type: "array",
41
- description: "附件文件/图片信息列表,无文件时可传null或空数组",
41
+ description: "附件文件/图片信息列表,无文件时可传null或空数组,支持传入数组或JSON字符串",
42
42
  items: {
43
43
  type: "object",
44
44
  properties: {
@@ -55,6 +55,10 @@ export function createAgentAsSkillTool(ctx) {
55
55
  type: "string",
56
56
  description: "文件可访问下载链接(完整HTTP/HTTPS地址)",
57
57
  },
58
+ fileUrlLocal: {
59
+ type: "string",
60
+ description: "文件本地路径,如果提供此字段,工具会自动上传文件并将公网URL填入fileUrl",
61
+ },
58
62
  },
59
63
  },
60
64
  },
@@ -71,6 +75,53 @@ export function createAgentAsSkillTool(ctx) {
71
75
  if (!params.query || typeof params.query !== "string") {
72
76
  throw new Error("Missing or invalid required parameter: query must be a non-empty string");
73
77
  }
78
+ // Robust parsing: normalize filesInfo from array or JSON string
79
+ let filesInfo = null;
80
+ if (params.filesInfo) {
81
+ if (Array.isArray(params.filesInfo)) {
82
+ filesInfo = params.filesInfo;
83
+ }
84
+ else if (typeof params.filesInfo === 'string') {
85
+ try {
86
+ const parsed = JSON.parse(params.filesInfo);
87
+ if (Array.isArray(parsed)) {
88
+ filesInfo = parsed;
89
+ }
90
+ else {
91
+ throw new Error("filesInfo must be an array or a JSON string representing an array");
92
+ }
93
+ }
94
+ catch (parseError) {
95
+ throw new Error(`filesInfo JSON解析失败: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
96
+ }
97
+ }
98
+ else {
99
+ filesInfo = null;
100
+ }
101
+ }
102
+ // Upload local files and fill fileUrl
103
+ if (filesInfo && filesInfo.length > 0) {
104
+ const uploadService = new XYFileUploadService(config.fileUploadUrl, config.apiKey, config.uid);
105
+ for (const fileInfo of filesInfo) {
106
+ if (fileInfo.fileUrlLocal && !fileInfo.fileUrl) {
107
+ try {
108
+ const publicUrl = await uploadService.uploadFileAndGetUrl(fileInfo.fileUrlLocal, "TEMPORARY_MATERIAL_DOC");
109
+ if (publicUrl) {
110
+ fileInfo.fileUrl = publicUrl;
111
+ }
112
+ else {
113
+ logger.warn("[AGENT-AS-SKILL] 上传文件未返回公网URL", { fileUrlLocal: fileInfo.fileUrlLocal });
114
+ }
115
+ }
116
+ catch (uploadError) {
117
+ logger.error("[AGENT-AS-SKILL] 上传本地文件失败", { fileUrlLocal: fileInfo.fileUrlLocal, error: uploadError });
118
+ throw new Error(`上传本地文件失败 (${fileInfo.fileUrlLocal}): ${uploadError instanceof Error ? uploadError.message : String(uploadError)}`);
119
+ }
120
+ }
121
+ // Remove fileUrlLocal from the final payload
122
+ delete fileInfo.fileUrlLocal;
123
+ }
124
+ }
74
125
  // Get WebSocket manager
75
126
  const wsManager = getXYWebSocketManager(config);
76
127
  // Build ExecuteAgentAsSkill command
@@ -82,7 +133,7 @@ export function createAgentAsSkillTool(ctx) {
82
133
  payload: {
83
134
  agentId: params.agentId,
84
135
  query: params.query,
85
- filesInfo: params.filesInfo || null,
136
+ filesInfo: filesInfo || null,
86
137
  },
87
138
  };
88
139
  // Send command and wait for response (5 minute timeout)
@@ -125,6 +176,7 @@ export function createAgentAsSkillTool(ctx) {
125
176
  taskId: currentTaskId,
126
177
  messageId,
127
178
  command,
179
+ toolCallId,
128
180
  }).then(() => {
129
181
  logger.log("[AGENT-AS-SKILL] Command sent successfully", { agentId: params.agentId });
130
182
  }).catch((error) => {
@@ -106,7 +106,7 @@ export function createCalendarTool(ctx) {
106
106
  });
107
107
  }
108
108
  else {
109
- reject(new Error(`创建日程失败: ${event.status}`));
109
+ reject(new Error(`创建日程失败: ${JSON.stringify(event.outputs)}`));
110
110
  }
111
111
  }
112
112
  };
@@ -120,6 +120,7 @@ export function createCalendarTool(ctx) {
120
120
  taskId: currentTaskId,
121
121
  messageId,
122
122
  command,
123
+ toolCallId,
123
124
  })
124
125
  .then(() => {
125
126
  })
@@ -22,7 +22,6 @@ import { createUploadFileTool } from "./upload-file-tool.js";
22
22
  import { createSaveFileToPhoneTool } from "./save-file-to-phone-tool.js";
23
23
  import { createSendEmailTool } from "./send-email-tool.js";
24
24
  import { createSearchEmailTool } from "./search-email-tool.js";
25
- import { createFindPcDevicesTool } from "./find-pc-devices-tool.js";
26
25
  import { sendStatusUpdate } from "../formatter.js";
27
26
  import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
28
27
  /**
@@ -55,7 +54,6 @@ export function createCallDeviceTool(ctx) {
55
54
  const searchFileTool = createSearchFileTool(ctx);
56
55
  const saveFileToPhoneTool = createSaveFileToPhoneTool(ctx);
57
56
  const searchEmailTool = createSearchEmailTool(ctx);
58
- const findPcDevicesTool = createFindPcDevicesTool(ctx);
59
57
  /**
60
58
  * 端工具注册表 —— 按 name 索引所有可通过 call_device_tool 调度的工具。
61
59
  */
@@ -84,7 +82,6 @@ export function createCallDeviceTool(ctx) {
84
82
  [saveFileToPhoneTool.name, saveFileToPhoneTool],
85
83
  [sendEmailTool.name, sendEmailTool],
86
84
  [searchEmailTool.name, searchEmailTool],
87
- [findPcDevicesTool.name, findPcDevicesTool],
88
85
  ]);
89
86
  return {
90
87
  name: "call_device_tool",
@@ -98,7 +98,7 @@ export function createCallPhoneTool(ctx) {
98
98
  });
99
99
  }
100
100
  else {
101
- reject(new Error(`拨打电话失败: ${event.status}`));
101
+ reject(new Error(`拨打电话失败: ${JSON.stringify(event.outputs)}`));
102
102
  }
103
103
  }
104
104
  };
@@ -112,6 +112,7 @@ export function createCallPhoneTool(ctx) {
112
112
  taskId: currentTaskId,
113
113
  messageId,
114
114
  command,
115
+ toolCallId,
115
116
  })
116
117
  .then(() => {
117
118
  })
@@ -244,7 +244,7 @@ b. 使用该工具之前需获取当前真实时间
244
244
  });
245
245
  }
246
246
  else {
247
- reject(new Error(`创建闹钟失败: ${event.status}`));
247
+ reject(new Error(`创建闹钟失败: ${JSON.stringify(event.outputs)}`));
248
248
  }
249
249
  }
250
250
  };
@@ -258,6 +258,7 @@ b. 使用该工具之前需获取当前真实时间
258
258
  taskId: currentTaskId,
259
259
  messageId,
260
260
  command,
261
+ toolCallId,
261
262
  })
262
263
  .then(() => {
263
264
  })
@@ -13,10 +13,12 @@ import { createGetPhotoToolSchemaTool } from "./get-photo-tool-schema.js";
13
13
  import { createGetDeviceFileToolSchemaTool } from "./get-device-file-tool-schema.js";
14
14
  import { createGetAlarmToolSchemaTool } from "./get-alarm-tool-schema.js";
15
15
  import { createGetCollectionToolSchemaTool } from "./get-collection-tool-schema.js";
16
- import { createGetEmailToolSchemaTool } from "./get-email-tool-schema.js";
16
+ // import { createGetEmailToolSchemaTool } from "./get-email-tool-schema.js";
17
17
  import { createLoginTokenTool } from "./login-token-tool.js";
18
18
  import { createAgentAsSkillTool } from "./agent-as-skill-tool.js";
19
- import { createFindPcDevicesTool } from "./find-pc-devices-tool.js";
19
+ import { createDiscoverCrossDevicesTool } from "./discover-cross-devices-tool.js";
20
+ import { createSendCrossDeviceTaskTool } from "./send-cross-device-task-tool.js";
21
+ import { createDisplayA2UICardTool } from "./display-a2ui-card-tool.js";
20
22
  import { logger } from "../utils/logger.js";
21
23
  /**
22
24
  * Create all XY channel tools for the given session context.
@@ -32,6 +34,9 @@ export function createAllTools(ctx) {
32
34
  logger.log(`[CREATE-ALL-TOOLS] creating tools`);
33
35
  return [
34
36
  createLocationTool(ctx),
37
+ createDiscoverCrossDevicesTool(ctx),
38
+ createSendCrossDeviceTaskTool(ctx),
39
+ createDisplayA2UICardTool(ctx),
35
40
  createCallDeviceTool(ctx),
36
41
  createGetNoteToolSchemaTool(ctx),
37
42
  createGetCalendarToolSchemaTool(ctx),
@@ -42,13 +47,12 @@ export function createAllTools(ctx) {
42
47
  createGetAlarmToolSchemaTool(ctx),
43
48
  createGetCollectionToolSchemaTool(ctx),
44
49
  createSendFileToUserTool(ctx),
45
- createGetEmailToolSchemaTool(ctx),
50
+ // createGetEmailToolSchemaTool(ctx),
46
51
  viewPushResultTool,
47
52
  createImageReadingTool(ctx),
48
53
  timestampToUtc8Tool,
49
54
  createSaveSelfEvolutionSkillTool(ctx),
50
55
  createLoginTokenTool(ctx),
51
56
  createAgentAsSkillTool(ctx),
52
- createFindPcDevicesTool(ctx),
53
57
  ];
54
58
  }
@@ -143,7 +143,7 @@ export function createDeleteAlarmTool(ctx) {
143
143
  });
144
144
  }
145
145
  else {
146
- reject(new Error(`删除闹钟失败: ${event.status}`));
146
+ reject(new Error(`删除闹钟失败: ${JSON.stringify(event.outputs)}`));
147
147
  }
148
148
  }
149
149
  };
@@ -157,6 +157,7 @@ export function createDeleteAlarmTool(ctx) {
157
157
  taskId: currentTaskId,
158
158
  messageId,
159
159
  command,
160
+ toolCallId,
160
161
  })
161
162
  .then(() => {
162
163
  })
@@ -1,4 +1,4 @@
1
1
  /** Known device type enum. */
2
- export declare const DEVICE_TYPES: readonly ["car", "2in1", "phone"];
2
+ export declare const DEVICE_TYPES: readonly ["car", "2in1", "phone", "web"];
3
3
  export type DeviceType = (typeof DEVICE_TYPES)[number];
4
4
  export declare function filterToolsByDevice(tools: any[], deviceType?: string): any[];
@@ -4,7 +4,7 @@
4
4
  // - denylist: listed tools are blocked, everything else is available (used for permissive devices like pc)
5
5
  // Tools NOT listed in any device entry → available to all devices (no restriction).
6
6
  /** Known device type enum. */
7
- export const DEVICE_TYPES = ["car", "2in1", "phone"];
7
+ export const DEVICE_TYPES = ["car", "2in1", "phone", "web"];
8
8
  const DEVICE_TOOL_POLICY = {
9
9
  "2in1": {
10
10
  allowlist: false,
@@ -15,10 +15,17 @@ const DEVICE_TOOL_POLICY = {
15
15
  "search_message",
16
16
  "search_contact",
17
17
  "get_contact_tool_schema",
18
- "query_collection",
19
- "add_collection",
20
- "delete_collection",
21
- "get_collection_tool_schema",
18
+ ],
19
+ },
20
+ "web": {
21
+ allowlist: true,
22
+ tools: [
23
+ "send_file_to_user",
24
+ "view_push_result",
25
+ "image_reading",
26
+ "convert_time_to_utc8_time",
27
+ "save_self_evolution_skill",
28
+ "displayA2UICard",
22
29
  ],
23
30
  },
24
31
  };
@@ -0,0 +1,2 @@
1
+ import type { SessionContext } from "./session-manager.js";
2
+ export declare function createDiscoverCrossDevicesTool(ctx: SessionContext): any;
@@ -0,0 +1,235 @@
1
+ import { sendCommand, sendStatusUpdate } from "../formatter.js";
2
+ import { getXYWebSocketManager } from "../client.js";
3
+ import { getCurrentMessageId, getCurrentTaskId } from "../task-manager.js";
4
+ import { logger } from "../utils/logger.js";
5
+ const DISCOVER_DEVICES_INTENT = "SearchAllDeviceInfo";
6
+ const DISCOVER_DEVICES_BUNDLE = "com.huawei.hmos.vassistant";
7
+ const DISCOVER_DEVICES_TIMEOUT_MS = 30_000;
8
+ const LOG_TAG = "[GetPCDeviceList]";
9
+ const DISCOVER_DEVICES_STATUS_TEXT = "正在查询设备列表...";
10
+ const DEVICE_TYPE_LABELS = {
11
+ "14": "phone",
12
+ "17": "pad",
13
+ "131": "car",
14
+ "2607": "PC",
15
+ };
16
+ function buildResultText(result) {
17
+ return {
18
+ content: [
19
+ {
20
+ type: "text",
21
+ text: JSON.stringify(result),
22
+ },
23
+ ],
24
+ };
25
+ }
26
+ function normalizeDevices(rawDevices) {
27
+ if (!Array.isArray(rawDevices)) {
28
+ return [];
29
+ }
30
+ return rawDevices
31
+ .filter((item) => Boolean(item) && typeof item === "object")
32
+ .map((device) => {
33
+ const networkId = typeof device.networkId === "string"
34
+ ? device.networkId
35
+ : typeof device.deviceId === "string"
36
+ ? device.deviceId
37
+ : "";
38
+ const deviceTypeId = typeof device.deviceTypeId === "string"
39
+ ? device.deviceTypeId
40
+ : typeof device.deviceType === "string"
41
+ ? device.deviceType
42
+ : "";
43
+ return {
44
+ networkId,
45
+ deviceName: typeof device.deviceName === "string" ? device.deviceName : "",
46
+ deviceTypeId,
47
+ deviceTypeLabel: DEVICE_TYPE_LABELS[deviceTypeId] ?? "unknown",
48
+ nearby: device.nearby === true,
49
+ rawDevice: device,
50
+ };
51
+ });
52
+ }
53
+ function inferDesiredDeviceTypes(query) {
54
+ const normalized = query.toLowerCase();
55
+ if (/(pc|computer|desktop|laptop|notebook)/iu.test(normalized) || /电脑|台式机|笔记本/iu.test(query)) {
56
+ return ["2607"];
57
+ }
58
+ if (/(tablet|pad|ipad)/iu.test(normalized) || /平板/iu.test(query)) {
59
+ return ["17"];
60
+ }
61
+ if (/(phone|mobile)/iu.test(normalized) || /手机/iu.test(query)) {
62
+ return ["14"];
63
+ }
64
+ return [];
65
+ }
66
+ function sortByNearby(devices) {
67
+ return [...devices].sort((a, b) => Number(b.nearby) - Number(a.nearby));
68
+ }
69
+ function recommendDevices(query, devices) {
70
+ const desiredTypes = inferDesiredDeviceTypes(query);
71
+ if (desiredTypes.length === 0) {
72
+ return {
73
+ recommendedDevices: [],
74
+ recommendationReason: "No explicit target device type was detected in the query.",
75
+ needsUserSelection: devices.length > 1,
76
+ selectionPrompt: devices.length > 1
77
+ ? "The query does not identify a unique device type. Ask the user to choose a target device by deviceName or networkId before sending a cross-device task."
78
+ : "",
79
+ };
80
+ }
81
+ const matches = devices.filter((device) => desiredTypes.includes(device.deviceTypeId));
82
+ if (matches.length === 0) {
83
+ return {
84
+ recommendedDevices: [],
85
+ recommendationReason: `No discovered device matches requested type(s): ${desiredTypes.join(", ")}.`,
86
+ needsUserSelection: false,
87
+ selectionPrompt: "",
88
+ };
89
+ }
90
+ const sortedMatches = sortByNearby(matches);
91
+ return {
92
+ recommendedDevices: sortedMatches,
93
+ recommendationReason: `Matched requested device type(s): ${desiredTypes.join(", ")}. Nearby devices are ranked first.`,
94
+ needsUserSelection: sortedMatches.length > 1,
95
+ selectionPrompt: sortedMatches.length > 1
96
+ ? "Multiple candidate devices match the user request. Ask the user to choose one target device by deviceName or networkId before calling send_cross_device_task."
97
+ : "",
98
+ };
99
+ }
100
+ export function createDiscoverCrossDevicesTool(ctx) {
101
+ const { config, sessionId, taskId, messageId } = ctx;
102
+ return {
103
+ name: "discover_cross_devices",
104
+ label: "发现跨设备协作设备",
105
+ description: `跨设备协作的设备发现工具。
106
+
107
+ 当用户明确表达要从另一台设备获取、查找、使用或操作内容时,必须优先调用本工具,例如从 PC、电脑、平板、手机等设备获取文件或查找内容。
108
+
109
+ 本工具只做设备发现和目标设备推荐,不会读取副设备文件内容,不会上传文件,也不会真正下发跨端执行任务。下发跨端执行任务需要使用SendCrossDeviceTaskTool`,
110
+ parameters: {
111
+ type: "object",
112
+ properties: {
113
+ query: {
114
+ type: "string",
115
+ description: "The user's original cross-device request, used to recommend the target device type.",
116
+ },
117
+ },
118
+ required: ["query"],
119
+ },
120
+ async execute(_toolCallId, params) {
121
+ const query = typeof params.query === "string" ? params.query.trim() : "";
122
+ logger.log(`${LOG_TAG} tool invoked`);
123
+ if (!query) {
124
+ return buildResultText({
125
+ success: false,
126
+ rawOutputs: null,
127
+ devices: [],
128
+ recommendedDevices: [],
129
+ recommendationReason: "",
130
+ message: "Missing required parameter: query.",
131
+ });
132
+ }
133
+ const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
134
+ const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
135
+ const wsManager = getXYWebSocketManager(config);
136
+ const command = {
137
+ header: {
138
+ namespace: "Common",
139
+ name: "Action",
140
+ },
141
+ payload: {
142
+ needUploadResult: true,
143
+ actionResponseConfig: {},
144
+ response: [],
145
+ executeParam: {
146
+ executeMode: "background",
147
+ intentName: DISCOVER_DEVICES_INTENT,
148
+ intentParam: {},
149
+ bundleName: DISCOVER_DEVICES_BUNDLE,
150
+ },
151
+ },
152
+ };
153
+ return new Promise((resolve) => {
154
+ let timeout;
155
+ let handler;
156
+ let settled = false;
157
+ const cleanup = () => {
158
+ clearTimeout(timeout);
159
+ wsManager.off("data-event", handler);
160
+ };
161
+ const finish = (result) => {
162
+ if (settled) {
163
+ return;
164
+ }
165
+ settled = true;
166
+ cleanup();
167
+ resolve(buildResultText(result));
168
+ };
169
+ handler = (event) => {
170
+ if (event.intentName !== DISCOVER_DEVICES_INTENT) {
171
+ return;
172
+ }
173
+ const rawOutputs = event.outputs ?? {};
174
+ const code = rawOutputs.code;
175
+ const success = event.status === "success" && String(code) === "0";
176
+ const devices = normalizeDevices(rawOutputs.result?.devices);
177
+ const recommendation = recommendDevices(query, devices);
178
+ logger.log(`${LOG_TAG} completed, success=${success}, deviceCount=${devices.length}, recommendedCount=${recommendation.recommendedDevices.length}`);
179
+ finish({
180
+ success,
181
+ rawOutputs,
182
+ devices,
183
+ recommendedDevices: recommendation.recommendedDevices,
184
+ recommendationReason: recommendation.recommendationReason,
185
+ needsUserSelection: recommendation.needsUserSelection,
186
+ selectionPrompt: recommendation.selectionPrompt,
187
+ message: success
188
+ ? recommendation.needsUserSelection
189
+ ? `Discovered ${devices.length} device(s). Multiple candidates may match; ask the user to choose the target device before sending a cross-device task.`
190
+ : `Discovered ${devices.length} device(s). The model should choose the final target device based on the user request.`
191
+ : "Device discovery failed on the device side.",
192
+ });
193
+ };
194
+ timeout = setTimeout(() => {
195
+ logger.log(`${LOG_TAG} timeout waiting UploadExeResult after ${DISCOVER_DEVICES_TIMEOUT_MS}ms`);
196
+ finish({
197
+ success: false,
198
+ rawOutputs: null,
199
+ devices: [],
200
+ recommendedDevices: [],
201
+ recommendationReason: "",
202
+ message: `Device discovery timed out after ${DISCOVER_DEVICES_TIMEOUT_MS / 1000} seconds.`,
203
+ });
204
+ }, DISCOVER_DEVICES_TIMEOUT_MS);
205
+ wsManager.on("data-event", handler);
206
+ sendStatusUpdate({
207
+ config,
208
+ sessionId,
209
+ taskId: currentTaskId,
210
+ messageId: currentMessageId,
211
+ text: DISCOVER_DEVICES_STATUS_TEXT,
212
+ state: "working",
213
+ })
214
+ .then(() => sendCommand({
215
+ config,
216
+ sessionId,
217
+ taskId: currentTaskId,
218
+ messageId: currentMessageId,
219
+ command,
220
+ }))
221
+ .catch((error) => {
222
+ logger.error(`${LOG_TAG} failed to send device discovery command: ${error instanceof Error ? error.message : String(error)}`);
223
+ finish({
224
+ success: false,
225
+ rawOutputs: null,
226
+ devices: [],
227
+ recommendedDevices: [],
228
+ recommendationReason: "",
229
+ message: `Failed to send device discovery command: ${error instanceof Error ? error.message : String(error)}`,
230
+ });
231
+ });
232
+ });
233
+ },
234
+ };
235
+ }
@@ -0,0 +1,2 @@
1
+ import type { SessionContext } from "./session-manager.js";
2
+ export declare function createDisplayA2UICardTool(ctx: SessionContext): any;
@@ -0,0 +1,85 @@
1
+ import { sendCommand } from "../formatter.js";
2
+ import { getCurrentMessageId, getCurrentTaskId } from "../task-manager.js";
3
+ import { logger } from "../utils/logger.js";
4
+ class ToolInputError extends Error {
5
+ status = 400;
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "ToolInputError";
9
+ }
10
+ }
11
+ function isJsonObjectOrArray(value) {
12
+ return !!value && typeof value === "object";
13
+ }
14
+ export function createDisplayA2UICardTool(ctx) {
15
+ const { config, sessionId, taskId, messageId } = ctx;
16
+ return {
17
+ name: "displayA2UICard",
18
+ label: "Display A2UI Card",
19
+ description: "当模型根据 MCP 工具返回结果判断需要向端侧下发 A2UI card 时调用。参数 cardId 和 cardData 由模型根据 MCP 工具返回结果传入,本工具只负责下发卡片展示指令。",
20
+ parameters: {
21
+ type: "object",
22
+ properties: {
23
+ cardId: {
24
+ type: "string",
25
+ description: "A2UI card 的唯一标识。",
26
+ },
27
+ cardData: {
28
+ oneOf: [
29
+ { type: "object" },
30
+ { type: "array" },
31
+ ],
32
+ description: "A2UI card 渲染所需的 JSON 对象或 JSON 数组,由模型根据 MCP 工具返回结果填充。",
33
+ },
34
+ },
35
+ required: ["cardId", "cardData"],
36
+ },
37
+ async execute(toolCallId, params) {
38
+ const cardId = typeof params?.cardId === "string" ? params.cardId.trim() : "";
39
+ const cardData = params?.cardData;
40
+ if (!cardId) {
41
+ throw new ToolInputError("缺少必填参数: cardId");
42
+ }
43
+ if (!isJsonObjectOrArray(cardData)) {
44
+ throw new ToolInputError("缺少必填参数: cardData,且必须是 JSON 对象或 JSON 数组");
45
+ }
46
+ const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
47
+ const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
48
+ const command = {
49
+ header: {
50
+ namespace: "Common",
51
+ name: "DisplayFACard",
52
+ },
53
+ payload: {
54
+ isA2ui: true,
55
+ a2uiParam: {
56
+ cardId,
57
+ cardData,
58
+ },
59
+ },
60
+ };
61
+ logger.log(`[DISPLAY-A2UI-CARD] sending card, cardId=${cardId}`);
62
+ await sendCommand({
63
+ config,
64
+ sessionId,
65
+ taskId: currentTaskId,
66
+ messageId: currentMessageId,
67
+ command,
68
+ toolCallId,
69
+ });
70
+ logger.log(`[DISPLAY-A2UI-CARD] card sent successfully, cardId=${cardId}`);
71
+ return {
72
+ content: [
73
+ {
74
+ type: "text",
75
+ text: JSON.stringify({
76
+ success: true,
77
+ cardId,
78
+ message: "A2UI card display command sent successfully.",
79
+ }),
80
+ },
81
+ ],
82
+ };
83
+ },
84
+ };
85
+ }
@@ -82,6 +82,7 @@ export function createFindPcDevicesTool(ctx) {
82
82
  taskId,
83
83
  messageId,
84
84
  command,
85
+ toolCallId,
85
86
  }).then(() => {
86
87
  }).catch((error) => {
87
88
  clearTimeout(timeout);
@@ -6,7 +6,7 @@ export function createGetCollectionToolSchemaTool(ctx) {
6
6
  return createSchemaTool({
7
7
  name: "get_collection_tool_schema",
8
8
  label: "Get Collection Tool Schema",
9
- description: "获取可在用户设备上添加、检索、删除小艺收藏中的公共知识数据的相关端工具列表。",
9
+ description: "获取可在用户设备上添加、检索、删除小艺收藏(也叫小艺帮记)中的公共知识数据的相关端工具列表。",
10
10
  tools: [createXiaoyiAddCollectionTool(ctx), createXiaoyiCollectionTool(ctx), createXiaoyiDeleteCollectionTool(ctx)],
11
11
  });
12
12
  }
@@ -2,14 +2,13 @@ import { createSchemaTool } from "./schema-tool-factory.js";
2
2
  import { createSearchFileTool } from "./search-file-tool.js";
3
3
  import { createUploadFileTool } from "./upload-file-tool.js";
4
4
  import { createSaveFileToPhoneTool } from "./save-file-to-phone-tool.js";
5
- import { createFindPcDevicesTool } from "./find-pc-devices-tool.js";
6
5
  export function createGetDeviceFileToolSchemaTool(ctx) {
7
6
  const searchFileTool = createSearchFileTool(ctx);
8
7
  const saveFileToPhoneTool = createSaveFileToPhoneTool(ctx);
9
8
  return createSchemaTool({
10
9
  name: "get_device_file_tool_schema",
11
10
  label: "Get Device File Tool Schema",
12
- description: "获取可在用户设备上搜索文件系统的文件、将用户设备本地文件上传到公网并获取链接、保存文件到文件管理器、查找PC设备的相关端工具列表。",
13
- tools: [searchFileTool, createUploadFileTool(ctx), saveFileToPhoneTool, createFindPcDevicesTool(ctx)],
11
+ description: "获取可在用户设备上搜索文件系统的文件、将用户设备本地文件上传到公网并获取链接、保存文件到文件管理器的相关端工具列表。",
12
+ tools: [searchFileTool, createUploadFileTool(ctx), saveFileToPhoneTool],
14
13
  });
15
14
  }
@@ -75,7 +75,7 @@ export function createLocationTool(ctx) {
75
75
  });
76
76
  }
77
77
  else {
78
- reject(new Error(`获取位置失败: ${event.status}`));
78
+ reject(new Error(`获取位置失败: ${JSON.stringify(event.outputs)}`));
79
79
  }
80
80
  }
81
81
  };
@@ -90,6 +90,7 @@ export function createLocationTool(ctx) {
90
90
  taskId: currentTaskId,
91
91
  messageId,
92
92
  command,
93
+ toolCallId,
93
94
  }).then(() => {
94
95
  }).catch((error) => {
95
96
  clearTimeout(timeout);