@ynhcj/xiaoyi-channel 0.0.160-beta → 0.0.162-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.
@@ -46,6 +46,36 @@ export function extractDataEvents(parts) {
46
46
  .filter((event) => event !== undefined);
47
47
  }
48
48
  export function extractRunCrossTaskContext(parts) {
49
+ const normalizeSentFiles = (value) => {
50
+ if (!Array.isArray(value)) {
51
+ return [];
52
+ }
53
+ return value
54
+ .map((item) => {
55
+ if (!item || typeof item !== "object") {
56
+ return null;
57
+ }
58
+ const candidate = item;
59
+ const fileLocalUrls = Array.isArray(candidate.fileLocalUrls)
60
+ ? candidate.fileLocalUrls.filter((url) => typeof url === "string" && url.length > 0)
61
+ : [];
62
+ const fileRemoteUrls = Array.isArray(candidate.fileRemoteUrls)
63
+ ? candidate.fileRemoteUrls.filter((url) => typeof url === "string" && url.length > 0)
64
+ : [];
65
+ const fileNames = Array.isArray(candidate.fileNames)
66
+ ? candidate.fileNames.filter((name) => typeof name === "string" && name.length > 0)
67
+ : [];
68
+ if (fileLocalUrls.length === 0 && fileRemoteUrls.length === 0) {
69
+ return null;
70
+ }
71
+ return {
72
+ ...(fileLocalUrls.length > 0 ? { fileLocalUrls } : {}),
73
+ ...(fileRemoteUrls.length > 0 ? { fileRemoteUrls } : {}),
74
+ ...(fileNames.length > 0 && fileNames.length === fileRemoteUrls.length ? { fileNames } : {}),
75
+ };
76
+ })
77
+ .filter((item) => item !== null);
78
+ };
49
79
  for (const part of parts) {
50
80
  if (part.kind !== "data" || !part.data) {
51
81
  continue;
@@ -64,7 +94,7 @@ export function extractRunCrossTaskContext(parts) {
64
94
  isDistributed: context.isDistributed === true,
65
95
  networkId,
66
96
  isSupportAgent: context.isSupportAgent !== false,
67
- fileUrls: Array.isArray(context.fileUrls) ? context.fileUrls.filter((url) => typeof url === "string") : [],
97
+ sentFiles: normalizeSentFiles(context.sentFiles),
68
98
  rawContext: context,
69
99
  };
70
100
  }
@@ -536,26 +536,28 @@ export const xiaoyiProvider = {
536
536
  const beforeLen = sp.length;
537
537
  // 删除 ## Tooling 与 TOOLS.md 声明之间的内容
538
538
  sp = sp.replace(/(## Tooling)[\s\S]*?(TOOLS\.md does not control tool availability; it is user guidance for how to use external tools\.)/, "$1\n\n$2");
539
- // (1) 提取 ## Skills (mandatory) </available_skills> 作为第一部分
540
- const skillsMatch = sp.match(/(## Skills \(mandatory\)[\s\S]*?<\/available_skills>)/);
541
- const part1 = skillsMatch ? skillsMatch[0] : '';
542
- // (2) 提取 ## /home/sandbox/.openclaw/workspace/SOUL.md 到 ## /home/sandbox/.openclaw/workspace/TOOLS.md 之前的内容作为第二部分
543
- const soulMatch = sp.match(/(## \/home\/sandbox\/\.openclaw\/workspace\/SOUL\.md[\s\S]*?)(?=## \/home\/sandbox\/\.openclaw\/workspace\/TOOLS\.md)/);
544
- const part2 = soulMatch ? soulMatch[1].trim() : '';
545
- if (part1 || part2) {
546
- // 从原始位置删除已提取的部分
547
- if (skillsMatch)
548
- sp = sp.replace(skillsMatch[0], '');
549
- if (soulMatch)
539
+ // (1) Skills 部分:移动到 ## Runtime 之前
540
+ if (sp.includes('## Runtime')) {
541
+ // 提取 ## Skills (mandatory) </available_skills> 作为第一部分
542
+ const skillsMatch = sp.match(/(## Skills \(mandatory\)[\s\S]*?<\/available_skills>)/);
543
+ if (skillsMatch) {
544
+ const part1 = skillsMatch[0];
545
+ sp = sp.replace(part1, '');
546
+ sp = sp.replace('## Runtime', part1 + '\n\n## Runtime');
547
+ }
548
+ }
549
+ // (2) SOUL.md 部分:移动到 ## Silent Replies 之前
550
+ if (sp.includes('## Silent Replies')) {
551
+ // 提取 ## /home/sandbox/.openclaw/workspace/SOUL.md 到 其特定脚注结束标志 的内容作为第二部分
552
+ const soulMatch = sp.match(/(## \/home\/sandbox\/\.openclaw\/workspace\/SOUL\.md[\s\S]*?_This file is yours to evolve\. As you learn who you are, update it\._)/);
553
+ if (soulMatch) {
554
+ const part2 = soulMatch[1].trim();
550
555
  sp = sp.replace(soulMatch[1], '');
551
- // 清理多余空行
552
- sp = sp.replace(/\n{3,}/g, '\n\n');
553
- // (3) 将 第二部分 + 第一部分 插入到 ## Runtime 上面
554
- const combined = (part2 + '\n\n' + part1).trim();
555
- if (combined && sp.includes('## Runtime')) {
556
- sp = sp.replace('## Runtime', combined + '\n\n## Runtime');
556
+ sp = sp.replace('## Silent Replies', part2 + '\n\n## Silent Replies');
557
557
  }
558
558
  }
559
+ // 清理多余空行
560
+ sp = sp.replace(/\n{3,}/g, '\n\n');
559
561
  logger.log(`[xiaoyiprovider] system prompt optimized: ${beforeLen} -> ${sp.length}`);
560
562
  context.systemPrompt = sp;
561
563
  }
@@ -23,7 +23,7 @@ function buildDistributionStatusCommand(context) {
23
23
  },
24
24
  };
25
25
  }
26
- function buildCrossTaskExecuteResultCommand(code, message, fileUrls = []) {
26
+ function buildCrossTaskExecuteResultCommand(code, message, sentFiles = []) {
27
27
  return {
28
28
  header: {
29
29
  namespace: "DistributionInteraction",
@@ -32,15 +32,15 @@ function buildCrossTaskExecuteResultCommand(code, message, fileUrls = []) {
32
32
  payload: {
33
33
  code,
34
34
  message,
35
- fileUrls,
35
+ sentFiles,
36
36
  },
37
37
  };
38
38
  }
39
39
  async function sendRunCrossTaskResult(params) {
40
40
  const { config, sessionId, taskId, messageId, context, resultCode, resultMessage } = params;
41
- const fileUrls = Array.isArray(context.fileUrls) ? context.fileUrls : [];
41
+ const sentFiles = Array.isArray(context.sentFiles) ? context.sentFiles : [];
42
42
  const statusCommand = buildDistributionStatusCommand(context);
43
- const resultCommand = buildCrossTaskExecuteResultCommand(resultCode, resultMessage, fileUrls);
43
+ const resultCommand = buildCrossTaskExecuteResultCommand(resultCode, resultMessage, sentFiles);
44
44
  await sendCommand({
45
45
  config,
46
46
  sessionId,
@@ -48,7 +48,7 @@ async function sendRunCrossTaskResult(params) {
48
48
  messageId,
49
49
  commands: [statusCommand, resultCommand],
50
50
  });
51
- logger.log(`${RUN_CROSS_TASK_LOG_TAG} sent cross-task result, sessionId=${sessionId}, taskId=${taskId}, code=${resultCode}, fileUrlCount=${fileUrls.length}, messageLength=${resultMessage.length}`);
51
+ logger.log(`${RUN_CROSS_TASK_LOG_TAG} sent cross-task result, sessionId=${sessionId}, taskId=${taskId}, code=${resultCode}, sentFileCount=${sentFiles.length}, messageLength=${resultMessage.length}`);
52
52
  }
53
53
  /**
54
54
  * 清理 /tmp/xy_channel 目录中超过 24 小时的旧文件
@@ -321,6 +321,7 @@ export function createXYReplyDispatcher(params) {
321
321
  dispatcher,
322
322
  replyOptions: {
323
323
  ...replyOptions,
324
+ suppressToolErrorWarnings: true,
324
325
  onModelSelected: prefixContext.onModelSelected,
325
326
  onToolStart: async ({ name, phase }) => {
326
327
  // 🔑 steered dispatch不发送tool状态(让主dispatcher处理)
@@ -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",
@@ -16,7 +16,6 @@ import { createGetCollectionToolSchemaTool } from "./get-collection-tool-schema.
16
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";
20
19
  import { createDiscoverCrossDevicesTool } from "./discover-cross-devices-tool.js";
21
20
  import { createSendCrossDeviceTaskTool } from "./send-cross-device-task-tool.js";
22
21
  import { logger } from "../utils/logger.js";
@@ -53,6 +52,5 @@ export function createAllTools(ctx) {
53
52
  createSaveSelfEvolutionSkillTool(ctx),
54
53
  createLoginTokenTool(ctx),
55
54
  createAgentAsSkillTool(ctx),
56
- createFindPcDevicesTool(ctx),
57
55
  ];
58
56
  }
@@ -106,7 +106,7 @@ export function createDiscoverCrossDevicesTool(ctx) {
106
106
 
107
107
  当用户明确表达要从另一台设备获取、查找、使用或操作内容时,必须优先调用本工具,例如从 PC、电脑、平板、手机等设备获取文件或查找内容。
108
108
 
109
- 本工具只做设备发现和目标设备推荐,不会读取副设备文件内容,不会上传文件,也不会真正下发跨端执行任务。`,
109
+ 本工具只做设备发现和目标设备推荐,不会读取副设备文件内容,不会上传文件,也不会真正下发跨端执行任务。下发跨端执行任务需要使用SendCrossDeviceTaskTool`,
110
110
  parameters: {
111
111
  type: "object",
112
112
  properties: {
@@ -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
  }
@@ -11,13 +11,12 @@ export function createSearchFileTool(ctx) {
11
11
  return {
12
12
  name: "search_file",
13
13
  label: "Search File",
14
- description: `搜索手机或PC/电脑文件系统的文件。
14
+ description: `搜索用户设备的文件系统的文件,用户设备可以是手机或者鸿蒙PC等。
15
15
 
16
- 【重要】使用场景与调用流程:
17
- 1. 搜索手机文件:当用户明确说明要从手机搜索时(如"从手机里面搜索xxxx"、"在手机上查找文件xxxx"),直接调用此工具,无需传入 udid。
18
- 2. 搜索PC/电脑文件:当用户要求搜索PC/电脑上的文件时(如"帮我找一下PC上的xxx文件"、"搜索电脑上的xxx"),必须先调用 find_pc_devices 工具获取设备ID(udid),然后将 udid 传入此工具进行搜索。
16
+ 使用场景与调用流程:
17
+ 当用户明确说明要从设备搜索时(如"从手机里面搜索xxxx"、"在手机上查找文件xxxx","查找我电脑上的xxxx文件","查找我鸿蒙PC上的xxx文件"),直接调用此工具。
19
18
 
20
- 如果用户没有明确说明从手机或PC搜索(如仅说"搜索文件"、"找一下xxxx"),应默认从当前runtime运行环境的本地文件系统查询,不要调用此工具。
19
+ 如果用户没有明确说明从设备搜索(如仅说"搜索文件"、"找一下xxxx"),应默认从当前runtime运行环境的本地文件系统查询,不要调用此工具。
21
20
 
22
21
  功能说明:根据关键词搜索文件名称或内容,返回匹配的文件列表(包括文件名、路径、大小、修改时间等信息)。
23
22
 
@@ -28,11 +27,7 @@ export function createSearchFileTool(ctx) {
28
27
  query: {
29
28
  type: "string",
30
29
  description: "搜索关键词,用于匹配文件名称、后缀名或文件内容",
31
- },
32
- udid: {
33
- type: "string",
34
- description: "PC/电脑设备ID。当搜索PC/电脑上的文件时,需要先通过 find_pc_devices 工具获取设备ID后传入。搜索手机文件时不需要传入此参数。",
35
- },
30
+ }
36
31
  },
37
32
  required: ["query"],
38
33
  },
@@ -18,9 +18,9 @@ function buildResultText(result) {
18
18
  };
19
19
  }
20
20
  function buildModelToolResult(result) {
21
- const fileUrls = result.fileUrls;
21
+ const sentFiles = result.sentFiles;
22
22
  const resultStatus = result.success
23
- ? fileUrls.length > 0
23
+ ? sentFiles.length > 0
24
24
  ? "对端设备执行任务成功且返回有文件"
25
25
  : "对端设备执行任务成功且返回无文件"
26
26
  : "对端设备任务失败";
@@ -46,35 +46,31 @@ function buildModelToolResult(result) {
46
46
  resultStatus,
47
47
  };
48
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
49
  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
50
  const result = {
59
51
  success: params.success,
60
52
  code: params.code,
61
53
  message: params.message,
62
- fileUrls,
54
+ sentFiles: params.sentFiles,
63
55
  rawEvent: params.rawEvent,
64
56
  };
65
57
  return result;
66
58
  }
67
59
  async function autoSendFileToUserIfNeeded(result, ctx) {
68
- const fileUrls = result.fileUrls;
69
- if (fileUrls.length === 0) {
60
+ const sentFiles = Array.isArray(result.sentFiles) ? result.sentFiles : [];
61
+ if (sentFiles.length === 0) {
70
62
  return result;
71
63
  }
72
- logger.log(`${SEND_CROSS_RESULT_LOG_TAG} auto sending ${fileUrls.length} cross-device file(s) to user`);
64
+ logger.log(`${SEND_CROSS_RESULT_LOG_TAG} auto sending ${sentFiles.length} cross-device file(s) to user`);
73
65
  try {
74
66
  const sendFileTool = createSendFileToUserTool(ctx);
75
- const sendFileResult = await sendFileTool.execute("auto_send_cross_device_file", {
76
- fileRemoteUrls: fileUrls,
77
- });
67
+ const sendFileResult = await (async () => {
68
+ const results = [];
69
+ for (const sentFileParams of sentFiles) {
70
+ results.push(await sendFileTool.execute("auto_send_cross_device_file", sentFileParams));
71
+ }
72
+ return results;
73
+ })();
78
74
  logger.log(`${SEND_CROSS_RESULT_LOG_TAG} auto send_file_to_user completed`);
79
75
  return {
80
76
  ...result,
@@ -222,7 +218,7 @@ export function createSendCrossDeviceTaskTool(ctx) {
222
218
  }
223
219
  settled = true;
224
220
  const modelResult = buildModelToolResult(result);
225
- logger.log(`${LOG_TAG} completed, success=${result.success}, code=${result.code}, fileUrlCount=${result.fileUrls.length}`);
221
+ logger.log(`${LOG_TAG} completed, success=${result.success}, code=${result.code}, sentFileCount=${result.sentFiles.length}`);
226
222
  cleanup();
227
223
  resolve(buildResultText(modelResult));
228
224
  };
@@ -230,7 +226,7 @@ export function createSendCrossDeviceTaskTool(ctx) {
230
226
  if (event.sessionId && event.sessionId !== sessionId && event.sessionId !== distributionSessionId) {
231
227
  return;
232
228
  }
233
- logger.log(`${SEND_CROSS_RESULT_LOG_TAG} received result, status=${event.status}, code=${event.code}, fileUrlCount=${event.fileUrls.length}`);
229
+ logger.log(`${SEND_CROSS_RESULT_LOG_TAG} received result, status=${event.status}, code=${event.code}, sentFileCount=${event.sentFiles.length}`);
234
230
  void (async () => {
235
231
  if (resultHandlingStarted) {
236
232
  return;
@@ -254,7 +250,7 @@ export function createSendCrossDeviceTaskTool(ctx) {
254
250
  success: event.status === "success",
255
251
  code: event.code,
256
252
  message: event.message,
257
- fileUrls: event.fileUrls,
253
+ sentFiles: event.sentFiles,
258
254
  rawEvent: event.rawEvent,
259
255
  });
260
256
  const resultWithFileSend = await autoSendFileToUserIfNeeded(result, ctx);
@@ -267,7 +263,7 @@ export function createSendCrossDeviceTaskTool(ctx) {
267
263
  success: false,
268
264
  code: "",
269
265
  message: `Cross-device task timed out after ${CROSS_DEVICE_TASK_TIMEOUT_MS / 1000} seconds.`,
270
- fileUrls: [],
266
+ sentFiles: [],
271
267
  rawEvent: null,
272
268
  });
273
269
  }, CROSS_DEVICE_TASK_TIMEOUT_MS);
@@ -293,7 +289,7 @@ export function createSendCrossDeviceTaskTool(ctx) {
293
289
  success: false,
294
290
  code: "",
295
291
  message: `Failed to send cross-device task command: ${error instanceof Error ? error.message : String(error)}`,
296
- fileUrls: [],
292
+ sentFiles: [],
297
293
  rawEvent: null,
298
294
  });
299
295
  });
@@ -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 send file to user tool - sends local files or remote files to user's device.
4
4
  * Supports both local file paths and remote URLs.
@@ -1,5 +1,6 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { XYFileUploadService } from "../file-upload.js";
3
+ import { appendRunCrossTaskSentFiles } from "./session-manager.js";
3
4
  import { logger } from "../utils/logger.js";
4
5
  import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
5
6
  import fetch from "node-fetch";
@@ -173,6 +174,16 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
173
174
  throw new Error(`fileNames length (${fileNames.length}) must match fileRemoteUrls length (${fileRemoteUrls.length})`);
174
175
  }
175
176
  }
177
+ if (ctx.runCrossTaskContext && (fileLocalUrls.length > 0 || fileRemoteUrls.length > 0)) {
178
+ const cachedSentFiles = appendRunCrossTaskSentFiles([
179
+ {
180
+ ...(fileLocalUrls.length > 0 ? { fileLocalUrls } : {}),
181
+ ...(fileRemoteUrls.length > 0 ? { fileRemoteUrls } : {}),
182
+ ...(fileNames.length > 0 ? { fileNames } : {}),
183
+ },
184
+ ], ctx.runCrossTaskContext);
185
+ logger.log(`[RunCrossTask] cached ${cachedSentFiles.length} send_file_to_user input(s) for cross-task result`);
186
+ }
176
187
  // Get WebSocket manager
177
188
  const wsManager = getXYWebSocketManager(config);
178
189
  // Create upload service
@@ -1,5 +1,5 @@
1
1
  import { AsyncLocalStorage } from "async_hooks";
2
- import type { RunCrossTaskContext, XYChannelConfig } from "../types.js";
2
+ import type { RunCrossTaskContext, SentFileParams, XYChannelConfig } from "../types.js";
3
3
  export interface SessionContext {
4
4
  config: XYChannelConfig;
5
5
  sessionId: string;
@@ -76,5 +76,5 @@ export declare function cleanupStaleSessions(): number;
76
76
  * Get the current number of active sessions (for diagnostics).
77
77
  */
78
78
  export declare function getActiveSessionCount(): number;
79
- export declare function appendRunCrossTaskFileUrls(fileUrls: string[], explicitRunCrossTaskContext?: RunCrossTaskContext): string[];
79
+ export declare function appendRunCrossTaskSentFiles(sentFiles: SentFileParams[], explicitRunCrossTaskContext?: RunCrossTaskContext): SentFileParams[];
80
80
  export {};
@@ -237,18 +237,40 @@ export function cleanupStaleSessions() {
237
237
  export function getActiveSessionCount() {
238
238
  return activeSessions.size;
239
239
  }
240
- export function appendRunCrossTaskFileUrls(fileUrls, explicitRunCrossTaskContext) {
240
+ function normalizeSentFileParams(params) {
241
+ const fileLocalUrls = Array.isArray(params.fileLocalUrls)
242
+ ? params.fileLocalUrls.filter((url) => typeof url === "string" && url.length > 0)
243
+ : [];
244
+ const fileRemoteUrls = Array.isArray(params.fileRemoteUrls)
245
+ ? params.fileRemoteUrls.filter((url) => typeof url === "string" && url.length > 0)
246
+ : [];
247
+ const fileNames = Array.isArray(params.fileNames)
248
+ ? params.fileNames.filter((name) => typeof name === "string" && name.length > 0)
249
+ : [];
250
+ if (fileLocalUrls.length === 0 && fileRemoteUrls.length === 0) {
251
+ return null;
252
+ }
253
+ return {
254
+ ...(fileLocalUrls.length > 0 ? { fileLocalUrls } : {}),
255
+ ...(fileRemoteUrls.length > 0 ? { fileRemoteUrls } : {}),
256
+ ...(fileNames.length > 0 && fileNames.length === fileRemoteUrls.length ? { fileNames } : {}),
257
+ };
258
+ }
259
+ export function appendRunCrossTaskSentFiles(sentFiles, explicitRunCrossTaskContext) {
241
260
  const context = asyncLocalStorage.getStore() ?? null;
242
261
  const runCrossTaskContext = explicitRunCrossTaskContext ?? context?.runCrossTaskContext;
243
- if (!runCrossTaskContext || fileUrls.length === 0) {
244
- return runCrossTaskContext?.fileUrls ?? [];
262
+ const normalizedSentFiles = sentFiles
263
+ .map((params) => normalizeSentFileParams(params))
264
+ .filter((params) => params !== null);
265
+ if (!runCrossTaskContext || normalizedSentFiles.length === 0) {
266
+ return runCrossTaskContext?.sentFiles ?? [];
245
267
  }
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;
268
+ const existing = Array.isArray(runCrossTaskContext.sentFiles) ? runCrossTaskContext.sentFiles : [];
269
+ const merged = [...existing, ...normalizedSentFiles];
270
+ runCrossTaskContext.sentFiles = merged;
249
271
  const sessionWithRef = Array.from(activeSessions.values()).find((session) => session.runCrossTaskContext === runCrossTaskContext);
250
272
  if (sessionWithRef?.runCrossTaskContext) {
251
- sessionWithRef.runCrossTaskContext.fileUrls = merged;
273
+ sessionWithRef.runCrossTaskContext.sentFiles = merged;
252
274
  }
253
275
  return merged;
254
276
  }
@@ -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,6 +1,5 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { sendCommand } from "../formatter.js";
3
- import { appendRunCrossTaskFileUrls } from "./session-manager.js";
4
3
  import { getCurrentTaskId } from "../task-manager.js";
5
4
  import { logger } from "../utils/logger.js";
6
5
  /**
@@ -20,19 +19,14 @@ export function createUploadFileTool(ctx) {
20
19
  return {
21
20
  name: "upload_file",
22
21
  label: "Upload File",
23
- description: `工具能力描述:将手机或PC/电脑本地文件上传并获取可公网访问的 URL。
22
+ description: `工具能力描述:将用户设备(可以使手机或者鸿蒙PC等)本地文件上传并获取可公网访问的 URL。
24
23
 
25
24
  前置工具调用:此工具使用前必须先通过call_device_tool调用 search_file 或者 query_collection 工具获取文件的 uri
26
25
 
27
- 使用场景与调用流程:
28
- 1. 上传手机文件:直接调用此工具,无需传入 udid。
29
- 2. 上传PC/电脑文件:当用户要求上传PC/电脑上的文件时,需要先通过 find_pc_devices 工具获取设备ID(udid),然后将 udid 传入此工具。
30
-
31
26
  工具参数说明:
32
27
  a. 入参中的fileInfos数组,每个元素必须包含mediaUri字段(对应于search_file工具或者query_collection返回结果中的uri),必须与search_file或者query_collection结果中对应的uri完全保持一致,不要自行修改。
33
28
  b. fileInfos中的timeout字段是可选的,表示上传文件超时时间,单位是毫秒,默认是20000(20秒)。
34
29
  c. fileInfos 是文件在本地的信息数组(从 search_file 工具或者query_collection 工具响应中获取)。限制:每次最多支持传入 5 条文件信息。
35
- d. udid 是PC/电脑设备ID,仅在上传PC/电脑文件时需要传入(通过 find_pc_devices 工具获取)。
36
30
 
37
31
  注意事项:
38
32
  a. 操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。
@@ -44,11 +38,7 @@ export function createUploadFileTool(ctx) {
44
38
  // 不指定 type,允许传入数组或 JSON 字符串
45
39
  // 具体的类型验证和转换在 execute 函数内部进行
46
40
  description: "文件信息数组,每个元素包含mediaUri(必需)和timeout(可选,默认20000)。必须先通过 search_file 工具获取。每次最多支持 5 条文件信息。",
47
- },
48
- udid: {
49
- type: "string",
50
- description: "PC/电脑设备ID。当上传PC/电脑上的文件时,需要先通过 find_pc_devices 工具获取设备ID后传入。上传手机文件时不需要传入此参数。",
51
- },
41
+ }
52
42
  },
53
43
  required: ["fileInfos"],
54
44
  },
@@ -108,10 +98,6 @@ export function createUploadFileTool(ctx) {
108
98
  // Get public URLs for the files
109
99
  const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
110
100
  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
- }
115
101
  return {
116
102
  content: [
117
103
  {
@@ -66,7 +66,7 @@ export interface CrossDeviceTaskResultEvent {
66
66
  sessionId: string;
67
67
  code: string;
68
68
  message: string;
69
- fileUrls: string[];
69
+ sentFiles: SentFileParams[];
70
70
  status: "success" | "failed";
71
71
  rawEvent: any;
72
72
  }
@@ -76,9 +76,14 @@ export interface RunCrossTaskContext {
76
76
  isDistributed: boolean;
77
77
  networkId: string;
78
78
  isSupportAgent: boolean;
79
- fileUrls?: string[];
79
+ sentFiles: SentFileParams[];
80
80
  rawContext: any;
81
81
  }
82
+ export interface SentFileParams {
83
+ fileLocalUrls?: string[];
84
+ fileRemoteUrls?: string[];
85
+ fileNames?: string[];
86
+ }
82
87
  export interface A2ATaskArtifactUpdateEvent {
83
88
  taskId: string;
84
89
  kind: "artifact-update";
@@ -392,15 +392,36 @@ export class XYWebSocketManager extends EventEmitter {
392
392
  }
393
393
  const code = item?.payload?.code === undefined ? "" : String(item.payload.code);
394
394
  const message = typeof item?.payload?.message === "string" ? item.payload.message : "";
395
- const fileUrls = Array.isArray(item?.payload?.fileUrls)
396
- ? item.payload.fileUrls.filter((url) => typeof url === "string" && url.length > 0)
395
+ const sentFiles = Array.isArray(item?.payload?.sentFiles)
396
+ ? item.payload.sentFiles.map((entry) => {
397
+ if (!entry || typeof entry !== "object") {
398
+ return null;
399
+ }
400
+ const fileLocalUrls = Array.isArray(entry.fileLocalUrls)
401
+ ? entry.fileLocalUrls.filter((url) => typeof url === "string" && url.length > 0)
402
+ : [];
403
+ const fileRemoteUrls = Array.isArray(entry.fileRemoteUrls)
404
+ ? entry.fileRemoteUrls.filter((url) => typeof url === "string" && url.length > 0)
405
+ : [];
406
+ const fileNames = Array.isArray(entry.fileNames)
407
+ ? entry.fileNames.filter((name) => typeof name === "string" && name.length > 0)
408
+ : [];
409
+ if (fileLocalUrls.length === 0 && fileRemoteUrls.length === 0) {
410
+ return null;
411
+ }
412
+ return {
413
+ ...(fileLocalUrls.length > 0 ? { fileLocalUrls } : {}),
414
+ ...(fileRemoteUrls.length > 0 ? { fileRemoteUrls } : {}),
415
+ ...(fileNames.length > 0 && fileNames.length === fileRemoteUrls.length ? { fileNames } : {}),
416
+ };
417
+ }).filter((entry) => entry !== null)
397
418
  : [];
398
419
  const status = code === "0" ? "success" : "failed";
399
420
  const event = {
400
421
  sessionId,
401
422
  code,
402
423
  message,
403
- fileUrls,
424
+ sentFiles,
404
425
  status,
405
426
  rawEvent: item,
406
427
  };
@@ -444,7 +465,7 @@ export class XYWebSocketManager extends EventEmitter {
444
465
  networkId,
445
466
  isDistributed: true,
446
467
  isSupportAgent: true,
447
- fileUrls: [],
468
+ sentFiles: [],
448
469
  rawContext: parsed,
449
470
  };
450
471
  const request = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.160-beta",
3
+ "version": "0.0.162-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",