@ynhcj/xiaoyi-channel 0.0.152-beta → 0.0.154-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) 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 +16 -0
  5. package/dist/src/cron-command.js +64 -0
  6. package/dist/src/formatter.d.ts +11 -1
  7. package/dist/src/formatter.js +22 -2
  8. package/dist/src/parser.d.ts +2 -1
  9. package/dist/src/parser.js +25 -0
  10. package/dist/src/reply-dispatcher.js +73 -1
  11. package/dist/src/tools/agent-as-skill-tool.js +1 -0
  12. package/dist/src/tools/calendar-tool.js +1 -0
  13. package/dist/src/tools/call-phone-tool.js +1 -0
  14. package/dist/src/tools/create-alarm-tool.js +1 -0
  15. package/dist/src/tools/create-all-tools.js +4 -0
  16. package/dist/src/tools/delete-alarm-tool.js +1 -0
  17. package/dist/src/tools/device-tool-map.d.ts +1 -1
  18. package/dist/src/tools/device-tool-map.js +11 -5
  19. package/dist/src/tools/discover-cross-devices-tool.d.ts +2 -0
  20. package/dist/src/tools/discover-cross-devices-tool.js +235 -0
  21. package/dist/src/tools/find-pc-devices-tool.js +1 -0
  22. package/dist/src/tools/location-tool.js +1 -0
  23. package/dist/src/tools/modify-alarm-tool.js +1 -0
  24. package/dist/src/tools/modify-note-tool.js +1 -0
  25. package/dist/src/tools/note-tool.js +1 -0
  26. package/dist/src/tools/query-app-message-tool.js +3 -2
  27. package/dist/src/tools/query-memory-data-tool.js +3 -2
  28. package/dist/src/tools/query-todo-task-tool.js +3 -2
  29. package/dist/src/tools/save-file-to-phone-tool.js +1 -0
  30. package/dist/src/tools/save-media-to-gallery-tool.js +1 -0
  31. package/dist/src/tools/search-alarm-tool.js +1 -0
  32. package/dist/src/tools/search-calendar-tool.js +1 -0
  33. package/dist/src/tools/search-contact-tool.js +1 -0
  34. package/dist/src/tools/search-email-tool.js +3 -2
  35. package/dist/src/tools/search-file-tool.js +1 -0
  36. package/dist/src/tools/search-message-tool.js +1 -0
  37. package/dist/src/tools/search-note-tool.js +1 -0
  38. package/dist/src/tools/search-photo-gallery-tool.js +3 -2
  39. package/dist/src/tools/send-cross-device-task-tool.d.ts +2 -0
  40. package/dist/src/tools/send-cross-device-task-tool.js +303 -0
  41. package/dist/src/tools/send-email-tool.js +3 -2
  42. package/dist/src/tools/send-message-tool.js +1 -0
  43. package/dist/src/tools/session-manager.d.ts +13 -1
  44. package/dist/src/tools/session-manager.js +38 -0
  45. package/dist/src/tools/upload-file-tool.d.ts +1 -1
  46. package/dist/src/tools/upload-file-tool.js +8 -2
  47. package/dist/src/tools/upload-photo-tool.js +3 -2
  48. package/dist/src/tools/xiaoyi-add-collection-tool.js +1 -0
  49. package/dist/src/tools/xiaoyi-collection-tool.js +1 -0
  50. package/dist/src/tools/xiaoyi-delete-collection-tool.js +1 -0
  51. package/dist/src/tools/xiaoyi-gui-tool.js +1 -0
  52. package/dist/src/types.d.ts +17 -0
  53. package/dist/src/websocket.d.ts +3 -0
  54. package/dist/src/websocket.js +168 -15
  55. package/package.json +2 -1
@@ -0,0 +1,303 @@
1
+ import { sendCommand, sendStatusUpdate } from "../formatter.js";
2
+ import { getXYWebSocketManager } from "../client.js";
3
+ import { getCurrentMessageId, getCurrentTaskId } from "../task-manager.js";
4
+ import { createSendFileToUserTool } from "./send-file-to-user-tool.js";
5
+ import { logger } from "../utils/logger.js";
6
+ const LOG_TAG = "[SendPcDeviceTask]";
7
+ const SEND_CROSS_RESULT_LOG_TAG = "[SendCrossResult]";
8
+ const CROSS_DEVICE_TASK_TIMEOUT_MS = 5 * 60_000;
9
+ const PEER_TASK_COMPLETED_STATUS_TEXT = "对端设备已完成当前任务,正在处理中 ...";
10
+ function buildResultText(result) {
11
+ return {
12
+ content: [
13
+ {
14
+ type: "text",
15
+ text: JSON.stringify(result),
16
+ },
17
+ ],
18
+ };
19
+ }
20
+ function buildModelToolResult(result) {
21
+ const fileUrls = result.fileUrls;
22
+ const resultStatus = result.success
23
+ ? fileUrls.length > 0
24
+ ? "对端设备执行任务成功且返回有文件"
25
+ : "对端设备执行任务成功且返回无文件"
26
+ : "对端设备任务失败";
27
+ const baseMessage = result.message || "对端设备未返回具体结果。";
28
+ let message = `跨端任务执行结果:${baseMessage}`;
29
+ if (resultStatus === "对端设备执行任务成功且返回有文件") {
30
+ if (result.autoSendFileToUser?.success) {
31
+ message += "\n\n对端设备返回了文件,系统已自动通过 send_file_to_user 将文件卡片发送给用户。请你基于跨端任务结果生成最终回复,告知用户任务已完成且文件已发送。";
32
+ }
33
+ else {
34
+ const errorMessage = result.autoSendFileToUser?.error || "未知错误";
35
+ message += `\n\n对端设备返回了文件,但系统自动发送文件卡片失败:${errorMessage}。请你向用户说明任务已完成但文件发送失败。`;
36
+ }
37
+ }
38
+ else if (resultStatus === "对端设备执行任务成功且返回无文件") {
39
+ message += "\n\n对端设备未返回文件。请你直接根据跨端任务结果向用户总结完成情况。";
40
+ }
41
+ else {
42
+ message += "\n\n对端设备任务失败。请你向用户说明失败情况,并给出可重试或调整任务描述的建议。";
43
+ }
44
+ return {
45
+ message,
46
+ resultStatus,
47
+ };
48
+ }
49
+ function extractFileUrl(message) {
50
+ const matches = message.match(/https?:\/\/[^\s<>"']+/g);
51
+ const firstUrl = matches?.[0] ?? "";
52
+ return firstUrl.replace(/[,。;、!?,.!?;:)\]}]+$/u, "");
53
+ }
54
+ function buildCrossDeviceResult(params) {
55
+ const payloadFileUrls = params.success ? params.fileUrls.filter((url) => typeof url === "string" && url.length > 0) : [];
56
+ const messageFileUrl = params.success ? extractFileUrl(params.message) : "";
57
+ const fileUrls = Array.from(new Set([...payloadFileUrls, ...(messageFileUrl ? [messageFileUrl] : [])]));
58
+ const result = {
59
+ success: params.success,
60
+ code: params.code,
61
+ message: params.message,
62
+ fileUrls,
63
+ rawEvent: params.rawEvent,
64
+ };
65
+ return result;
66
+ }
67
+ async function autoSendFileToUserIfNeeded(result, ctx) {
68
+ const fileUrls = result.fileUrls;
69
+ if (fileUrls.length === 0) {
70
+ return result;
71
+ }
72
+ logger.log(`${SEND_CROSS_RESULT_LOG_TAG} auto sending ${fileUrls.length} cross-device file(s) to user`);
73
+ try {
74
+ const sendFileTool = createSendFileToUserTool(ctx);
75
+ const sendFileResult = await sendFileTool.execute("auto_send_cross_device_file", {
76
+ fileRemoteUrls: fileUrls,
77
+ });
78
+ logger.log(`${SEND_CROSS_RESULT_LOG_TAG} auto send_file_to_user completed`);
79
+ return {
80
+ ...result,
81
+ autoSendFileToUser: {
82
+ success: true,
83
+ result: sendFileResult,
84
+ },
85
+ };
86
+ }
87
+ catch (error) {
88
+ const errorMessage = error instanceof Error ? error.message : String(error);
89
+ logger.error(`${SEND_CROSS_RESULT_LOG_TAG} auto send_file_to_user failed, error=${errorMessage}`);
90
+ return {
91
+ ...result,
92
+ autoSendFileToUser: {
93
+ success: false,
94
+ error: errorMessage,
95
+ },
96
+ };
97
+ }
98
+ }
99
+ function normalizeTargetDeviceInfo(value) {
100
+ if (!value || typeof value !== "object") {
101
+ return null;
102
+ }
103
+ const candidate = value;
104
+ const networkId = typeof candidate.networkId === "string"
105
+ ? candidate.networkId.trim()
106
+ : typeof candidate.deviceId === "string"
107
+ ? candidate.deviceId.trim()
108
+ : "";
109
+ const deviceName = typeof candidate.deviceName === "string" ? candidate.deviceName.trim() : "";
110
+ const deviceTypeId = typeof candidate.deviceTypeId === "string"
111
+ ? candidate.deviceTypeId.trim()
112
+ : typeof candidate.deviceType === "string"
113
+ ? candidate.deviceType.trim()
114
+ : "";
115
+ if (!networkId || !deviceName || !deviceTypeId) {
116
+ return null;
117
+ }
118
+ return {
119
+ networkId,
120
+ deviceName,
121
+ deviceTypeId,
122
+ };
123
+ }
124
+ function buildUnifiedDistributeCommand(query, targetDeviceInfo, distributionSessionId) {
125
+ return {
126
+ header: {
127
+ namespace: "DistributionInteraction",
128
+ name: "UnifiedDistribute",
129
+ },
130
+ payload: {
131
+ targetDeviceInfo,
132
+ crossDeviceContent: {
133
+ query,
134
+ contexts: {
135
+ agentClientContext: {
136
+ header: {
137
+ namespace: "System",
138
+ name: "ClientContext",
139
+ },
140
+ payload: {
141
+ agentId: "",
142
+ isSupportAgent: true,
143
+ distributionSessionId,
144
+ localNetworkId: targetDeviceInfo.networkId,
145
+ },
146
+ },
147
+ },
148
+ },
149
+ },
150
+ };
151
+ }
152
+ export function createSendCrossDeviceTaskTool(ctx) {
153
+ const { config, sessionId, taskId, messageId } = ctx;
154
+ return {
155
+ name: "send_cross_device_task",
156
+ label: "下发跨设备协作任务",
157
+ description: `向用户已经选定的目标设备下发跨设备协作任务。
158
+
159
+ 使用流程:
160
+ 1. 必须先调用 discover_cross_devices 获取设备列表。
161
+ 2. 根据用户原始需求选择唯一目标设备。
162
+ 3. 如果存在多个同类型候选设备,或无法判断目标设备,必须先询问用户选择设备,不要调用本工具。
163
+ 4. 只有当 targetDeviceInfo 中的 networkId、deviceName、deviceTypeId 都已明确时,才调用本工具。
164
+ 5. 传入的query必须是用户原始query,不要做任何更改。`,
165
+ parameters: {
166
+ type: "object",
167
+ properties: {
168
+ query: {
169
+ type: "string",
170
+ description: "用户原始跨设备任务需求,例如:从 PC 获取某文件。",
171
+ },
172
+ targetDeviceInfo: {
173
+ type: "object",
174
+ description: "模型从 discover_cross_devices 返回列表中选定的唯一目标设备。",
175
+ properties: {
176
+ networkId: {
177
+ type: "string",
178
+ description: "目标设备标识 networkId。",
179
+ },
180
+ deviceName: {
181
+ type: "string",
182
+ description: "目标设备名称。",
183
+ },
184
+ deviceTypeId: {
185
+ type: "string",
186
+ description: "目标设备类型编号 deviceTypeId,例如 14、17、131、2607。",
187
+ },
188
+ },
189
+ required: ["networkId", "deviceName", "deviceTypeId"],
190
+ },
191
+ },
192
+ required: ["query", "targetDeviceInfo"],
193
+ },
194
+ async execute(_toolCallId, params) {
195
+ const query = typeof params.query === "string" ? params.query.trim() : "";
196
+ const targetDeviceInfo = normalizeTargetDeviceInfo(params.targetDeviceInfo);
197
+ if (!query || !targetDeviceInfo) {
198
+ return buildResultText({
199
+ message: "Missing required parameters: query and targetDeviceInfo.networkId/deviceName/deviceTypeId.",
200
+ resultStatus: "对端设备任务失败",
201
+ });
202
+ }
203
+ const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
204
+ const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
205
+ const wsManager = getXYWebSocketManager(config);
206
+ const distributionSessionId = ctx.distributionSessionId || sessionId;
207
+ const command = buildUnifiedDistributeCommand(query, targetDeviceInfo, distributionSessionId);
208
+ const statusText = `正在调用${targetDeviceInfo.deviceName}执行“${query}”跨设备任务...`;
209
+ logger.log(`${LOG_TAG} sending task to ${targetDeviceInfo.deviceName}, distributionSessionId=${distributionSessionId}`);
210
+ return new Promise((resolve) => {
211
+ let timeout;
212
+ let handler;
213
+ let settled = false;
214
+ let resultHandlingStarted = false;
215
+ const cleanup = () => {
216
+ clearTimeout(timeout);
217
+ wsManager.off("cross-device-task-result", handler);
218
+ };
219
+ const finish = (result) => {
220
+ if (settled) {
221
+ return;
222
+ }
223
+ settled = true;
224
+ const modelResult = buildModelToolResult(result);
225
+ logger.log(`${LOG_TAG} completed, success=${result.success}, code=${result.code}, fileUrlCount=${result.fileUrls.length}`);
226
+ cleanup();
227
+ resolve(buildResultText(modelResult));
228
+ };
229
+ handler = (event) => {
230
+ if (event.sessionId && event.sessionId !== sessionId && event.sessionId !== distributionSessionId) {
231
+ return;
232
+ }
233
+ logger.log(`${SEND_CROSS_RESULT_LOG_TAG} received result, status=${event.status}, code=${event.code}, fileUrlCount=${event.fileUrls.length}`);
234
+ void (async () => {
235
+ if (resultHandlingStarted) {
236
+ return;
237
+ }
238
+ resultHandlingStarted = true;
239
+ clearTimeout(timeout);
240
+ try {
241
+ await sendStatusUpdate({
242
+ config,
243
+ sessionId,
244
+ taskId: currentTaskId,
245
+ messageId: currentMessageId,
246
+ text: PEER_TASK_COMPLETED_STATUS_TEXT,
247
+ state: "working",
248
+ });
249
+ }
250
+ catch (error) {
251
+ logger.error(`${SEND_CROSS_RESULT_LOG_TAG} failed to send peer task completed status update: ${error instanceof Error ? error.message : String(error)}`);
252
+ }
253
+ const result = buildCrossDeviceResult({
254
+ success: event.status === "success",
255
+ code: event.code,
256
+ message: event.message,
257
+ fileUrls: event.fileUrls,
258
+ rawEvent: event.rawEvent,
259
+ });
260
+ const resultWithFileSend = await autoSendFileToUserIfNeeded(result, ctx);
261
+ finish(resultWithFileSend);
262
+ })();
263
+ };
264
+ timeout = setTimeout(() => {
265
+ logger.log(`${LOG_TAG} timeout waiting cross-device result after ${CROSS_DEVICE_TASK_TIMEOUT_MS}ms`);
266
+ finish({
267
+ success: false,
268
+ code: "",
269
+ message: `Cross-device task timed out after ${CROSS_DEVICE_TASK_TIMEOUT_MS / 1000} seconds.`,
270
+ fileUrls: [],
271
+ rawEvent: null,
272
+ });
273
+ }, CROSS_DEVICE_TASK_TIMEOUT_MS);
274
+ wsManager.on("cross-device-task-result", handler);
275
+ sendStatusUpdate({
276
+ config,
277
+ sessionId,
278
+ taskId: currentTaskId,
279
+ messageId: currentMessageId,
280
+ text: statusText,
281
+ state: "working",
282
+ })
283
+ .then(() => sendCommand({
284
+ config,
285
+ sessionId,
286
+ taskId: currentTaskId,
287
+ messageId: currentMessageId,
288
+ command,
289
+ }))
290
+ .catch((error) => {
291
+ logger.error(`${LOG_TAG} failed to send cross-device task command: ${error instanceof Error ? error.message : String(error)}`);
292
+ finish({
293
+ success: false,
294
+ code: "",
295
+ message: `Failed to send cross-device task command: ${error instanceof Error ? error.message : String(error)}`,
296
+ fileUrls: [],
297
+ rawEvent: null,
298
+ });
299
+ });
300
+ });
301
+ },
302
+ };
303
+ }
@@ -43,7 +43,7 @@ c. 调用工具前需认真检查调用参数是否满足工具要求
43
43
  },
44
44
  required: ["subject", "to", "body"],
45
45
  },
46
- async execute(_toolCallId, params) {
46
+ async execute(toolCallId, params) {
47
47
  if (typeof params.subject !== "string" || !params.subject.trim()) {
48
48
  throw new ToolInputError("缺少必填参数 subject(邮件主题)");
49
49
  }
@@ -92,7 +92,7 @@ c. 调用工具前需认真检查调用参数是否满足工具要求
92
92
  return new Promise((resolve, reject) => {
93
93
  const timeout = setTimeout(() => {
94
94
  wsManager.off("data-event", handler);
95
- logger.error("超时: 发送邮件超时(60秒)", { toolCallId: _toolCallId });
95
+ logger.error("超时: 发送邮件超时(60秒)", { toolCallId: toolCallId });
96
96
  reject(new Error("发送邮件超时(60秒)"));
97
97
  }, 60000);
98
98
  const handler = (event) => {
@@ -122,6 +122,7 @@ c. 调用工具前需认真检查调用参数是否满足工具要求
122
122
  taskId: currentTaskId,
123
123
  messageId,
124
124
  command,
125
+ toolCallId,
125
126
  })
126
127
  .then(() => { })
127
128
  .catch((error) => {
@@ -124,6 +124,7 @@ export function createSendMessageTool(ctx) {
124
124
  taskId: currentTaskId,
125
125
  messageId,
126
126
  command,
127
+ toolCallId,
127
128
  })
128
129
  .then(() => {
129
130
  })
@@ -1,18 +1,29 @@
1
1
  import { AsyncLocalStorage } from "async_hooks";
2
- import type { XYChannelConfig } from "../types.js";
2
+ import type { RunCrossTaskContext, XYChannelConfig } from "../types.js";
3
3
  export interface SessionContext {
4
4
  config: XYChannelConfig;
5
5
  sessionId: string;
6
+ distributionSessionId?: string;
6
7
  taskId: string;
7
8
  messageId: string;
8
9
  agentId: string;
9
10
  deviceType?: string;
11
+ runCrossTaskContext?: RunCrossTaskContext;
12
+ /** When true, this context was created for a cron/scheduled task execution.
13
+ * Tools should use the push channel instead of WebSocket sendCommand. */
14
+ isCron?: boolean;
10
15
  }
11
16
  interface SessionContextWithRef extends SessionContext {
12
17
  refCount: number;
13
18
  createdAt: number;
14
19
  }
15
20
  export declare const activeSessions: Map<string, SessionContextWithRef>;
21
+ /** Mark a toolCallId as originating from a cron trigger. */
22
+ export declare function markCronToolCall(toolCallId: string): void;
23
+ /** Check whether a toolCallId is from a cron trigger. */
24
+ export declare function isCronToolCall(toolCallId?: string): boolean;
25
+ /** Clean up a cron tool call marker after use. */
26
+ export declare function clearCronToolCall(toolCallId: string): void;
16
27
  export declare const asyncLocalStorage: AsyncLocalStorage<SessionContext>;
17
28
  /**
18
29
  * Register a session context for tool access.
@@ -65,4 +76,5 @@ export declare function cleanupStaleSessions(): number;
65
76
  * Get the current number of active sessions (for diagnostics).
66
77
  */
67
78
  export declare function getActiveSessionCount(): number;
79
+ export declare function appendRunCrossTaskFileUrls(fileUrls: string[], explicitRunCrossTaskContext?: RunCrossTaskContext): string[];
68
80
  export {};
@@ -25,6 +25,29 @@ if (!_g.__xyLastRegisteredSessionKey) {
25
25
  }
26
26
  const getLastRegisteredKey = () => _g.__xyLastRegisteredSessionKey;
27
27
  const setLastRegisteredKey = (key) => { _g.__xyLastRegisteredSessionKey = key; };
28
+ // ── Cron tool-call tracking ─────────────────────────────────────────
29
+ // Global Map keyed by toolCallId to track whether a specific tool call
30
+ // originated from a cron/scheduled task. Populated by the
31
+ // `before_tool_call` hook (which receives openclaw's sessionKey with
32
+ // "cron:" prefix), consumed by sendCommand() to route through push channel.
33
+ if (!_g.__xyCronToolCallMap) {
34
+ _g.__xyCronToolCallMap = new Map();
35
+ }
36
+ const cronToolCallMap = _g.__xyCronToolCallMap;
37
+ /** Mark a toolCallId as originating from a cron trigger. */
38
+ export function markCronToolCall(toolCallId) {
39
+ cronToolCallMap.set(toolCallId, true);
40
+ }
41
+ /** Check whether a toolCallId is from a cron trigger. */
42
+ export function isCronToolCall(toolCallId) {
43
+ if (!toolCallId)
44
+ return false;
45
+ return cronToolCallMap.get(toolCallId) === true;
46
+ }
47
+ /** Clean up a cron tool call marker after use. */
48
+ export function clearCronToolCall(toolCallId) {
49
+ cronToolCallMap.delete(toolCallId);
50
+ }
28
51
  // AsyncLocalStorage for thread-safe session context isolation
29
52
  export const asyncLocalStorage = new AsyncLocalStorage();
30
53
  // Export AsyncLocalStorage to globalThis so logger.ts can access it
@@ -214,6 +237,21 @@ export function cleanupStaleSessions() {
214
237
  export function getActiveSessionCount() {
215
238
  return activeSessions.size;
216
239
  }
240
+ export function appendRunCrossTaskFileUrls(fileUrls, explicitRunCrossTaskContext) {
241
+ const context = asyncLocalStorage.getStore() ?? null;
242
+ const runCrossTaskContext = explicitRunCrossTaskContext ?? context?.runCrossTaskContext;
243
+ if (!runCrossTaskContext || fileUrls.length === 0) {
244
+ return runCrossTaskContext?.fileUrls ?? [];
245
+ }
246
+ const existing = Array.isArray(runCrossTaskContext.fileUrls) ? runCrossTaskContext.fileUrls : [];
247
+ const merged = Array.from(new Set([...existing, ...fileUrls.filter((url) => typeof url === "string" && url.length > 0)]));
248
+ runCrossTaskContext.fileUrls = merged;
249
+ const sessionWithRef = Array.from(activeSessions.values()).find((session) => session.runCrossTaskContext === runCrossTaskContext);
250
+ if (sessionWithRef?.runCrossTaskContext) {
251
+ sessionWithRef.runCrossTaskContext.fileUrls = merged;
252
+ }
253
+ return merged;
254
+ }
217
255
  /**
218
256
  * Enrich a base session context with the latest taskId/messageId
219
257
  * from task-manager (supports interruption scenarios).
@@ -1,4 +1,4 @@
1
- import type { SessionContext } from "./session-manager.js";
1
+ import { type SessionContext } from "./session-manager.js";
2
2
  /**
3
3
  * XY upload file tool - uploads local files to get publicly accessible URLs.
4
4
  * Requires file URIs from search_file tool as prerequisite.
@@ -1,5 +1,6 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { sendCommand } from "../formatter.js";
3
+ import { appendRunCrossTaskFileUrls } from "./session-manager.js";
3
4
  import { getCurrentTaskId } from "../task-manager.js";
4
5
  import { logger } from "../utils/logger.js";
5
6
  /**
@@ -106,7 +107,11 @@ export function createUploadFileTool(ctx) {
106
107
  const wsManager = getXYWebSocketManager(config);
107
108
  // Get public URLs for the files
108
109
  const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
109
- const fileUrls = await getFileUrls(wsManager, config, sessionId, currentTaskId, messageId, fileInfos, params.udid);
110
+ const fileUrls = await getFileUrls(wsManager, config, sessionId, currentTaskId, messageId, toolCallId, fileInfos, params.udid);
111
+ if (ctx.runCrossTaskContext && fileUrls.length > 0) {
112
+ const cachedFileUrls = appendRunCrossTaskFileUrls(fileUrls, ctx.runCrossTaskContext);
113
+ logger.log(`[RunCrossTask] cached ${fileUrls.length} upload_file URL(s) for cross-task result, total=${cachedFileUrls.length}`);
114
+ }
110
115
  return {
111
116
  content: [
112
117
  {
@@ -126,7 +131,7 @@ export function createUploadFileTool(ctx) {
126
131
  * Get public URLs for files using fileInfos
127
132
  * Returns array of publicly accessible file URLs
128
133
  */
129
- async function getFileUrls(wsManager, config, sessionId, taskId, messageId, fileInfos, udid) {
134
+ async function getFileUrls(wsManager, config, sessionId, taskId, messageId, toolCallId, fileInfos, udid) {
130
135
  const command = {
131
136
  header: {
132
137
  namespace: "Common",
@@ -214,6 +219,7 @@ async function getFileUrls(wsManager, config, sessionId, taskId, messageId, file
214
219
  taskId,
215
220
  messageId,
216
221
  command,
222
+ toolCallId,
217
223
  })
218
224
  .then(() => {
219
225
  })
@@ -78,7 +78,7 @@ export function createUploadPhotoTool(ctx) {
78
78
  const wsManager = getXYWebSocketManager(config);
79
79
  // Get public URLs for the photos
80
80
  const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
81
- const imageUrls = await getPhotoUrls(wsManager, config, sessionId, currentTaskId, messageId, mediaUris);
81
+ const imageUrls = await getPhotoUrls(wsManager, config, sessionId, currentTaskId, messageId, toolCallId, mediaUris);
82
82
  return {
83
83
  content: [
84
84
  {
@@ -98,7 +98,7 @@ export function createUploadPhotoTool(ctx) {
98
98
  * Get public URLs for photos using mediaUris
99
99
  * Returns array of publicly accessible image URLs
100
100
  */
101
- async function getPhotoUrls(wsManager, config, sessionId, taskId, messageId, mediaUris) {
101
+ async function getPhotoUrls(wsManager, config, sessionId, taskId, messageId, toolCallId, mediaUris) {
102
102
  // Build imageInfos array from mediaUris
103
103
  const imageInfos = mediaUris.map(mediaUri => ({ mediaUri }));
104
104
  const command = {
@@ -169,6 +169,7 @@ async function getPhotoUrls(wsManager, config, sessionId, taskId, messageId, med
169
169
  taskId,
170
170
  messageId,
171
171
  command,
172
+ toolCallId,
172
173
  })
173
174
  .then(() => {
174
175
  })
@@ -178,6 +178,7 @@ export function createXiaoyiAddCollectionTool(ctx) {
178
178
  taskId: currentTaskId,
179
179
  messageId,
180
180
  command,
181
+ toolCallId,
181
182
  })
182
183
  .then(() => {
183
184
  })
@@ -134,6 +134,7 @@ export function createXiaoyiCollectionTool(ctx) {
134
134
  taskId: currentTaskId,
135
135
  messageId,
136
136
  command,
137
+ toolCallId,
137
138
  })
138
139
  .then(() => {
139
140
  })
@@ -149,6 +149,7 @@ export function createXiaoyiDeleteCollectionTool(ctx) {
149
149
  taskId: currentTaskId,
150
150
  messageId,
151
151
  command,
152
+ toolCallId,
152
153
  })
153
154
  .then(() => {
154
155
  })
@@ -106,6 +106,7 @@ export function createXiaoyiGuiTool(ctx) {
106
106
  taskId: currentTaskId,
107
107
  messageId,
108
108
  command,
109
+ toolCallId,
109
110
  }).then(() => {
110
111
  }).catch((error) => {
111
112
  clearTimeout(timeout);
@@ -62,6 +62,23 @@ export interface A2ADataEvent {
62
62
  outputs: Record<string, any>;
63
63
  status: "success" | "failed";
64
64
  }
65
+ export interface CrossDeviceTaskResultEvent {
66
+ sessionId: string;
67
+ code: string;
68
+ message: string;
69
+ fileUrls: string[];
70
+ status: "success" | "failed";
71
+ rawEvent: any;
72
+ }
73
+ export interface RunCrossTaskContext {
74
+ agentId: string;
75
+ sessionId: string;
76
+ isDistributed: boolean;
77
+ networkId: string;
78
+ isSupportAgent: boolean;
79
+ fileUrls?: string[];
80
+ rawContext: any;
81
+ }
65
82
  export interface A2ATaskArtifactUpdateEvent {
66
83
  taskId: string;
67
84
  kind: "artifact-update";
@@ -102,6 +102,9 @@ export declare class XYWebSocketManager extends EventEmitter {
102
102
  * Start heartbeat.
103
103
  */
104
104
  private startHeartbeat;
105
+ private toUploadExeDataEvent;
106
+ private toCrossDeviceTaskResultEvent;
107
+ private toRunCrossTaskA2ARequest;
105
108
  /**
106
109
  * Handle incoming message from server.
107
110
  */