@ynhcj/xiaoyi-channel 0.0.52-beta → 0.0.52-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 (61) hide show
  1. package/dist/index.d.ts +0 -2
  2. package/dist/index.js +7 -6
  3. package/dist/src/bot.d.ts +1 -0
  4. package/dist/src/bot.js +44 -32
  5. package/dist/src/channel.js +32 -4
  6. package/dist/src/client.js +0 -6
  7. package/dist/src/cspl/call-api.js +19 -9
  8. package/dist/src/cspl/config.js +3 -3
  9. package/dist/src/cspl/constants.d.ts +2 -1
  10. package/dist/src/cspl/constants.js +1 -1
  11. package/dist/src/file-download.js +1 -1
  12. package/dist/src/file-upload.js +1 -11
  13. package/dist/src/formatter.d.ts +2 -0
  14. package/dist/src/formatter.js +11 -6
  15. package/dist/src/monitor.js +8 -10
  16. package/dist/src/onboarding.d.ts +3 -4
  17. package/dist/src/onboarding.js +2 -2
  18. package/dist/src/outbound.d.ts +2 -1
  19. package/dist/src/outbound.js +1 -19
  20. package/dist/src/parser.d.ts +6 -0
  21. package/dist/src/parser.js +16 -0
  22. package/dist/src/provider.d.ts +2 -0
  23. package/dist/src/provider.js +124 -0
  24. package/dist/src/push.js +0 -21
  25. package/dist/src/reply-dispatcher.d.ts +4 -0
  26. package/dist/src/reply-dispatcher.js +21 -13
  27. package/dist/src/thread-bindings.d.ts +54 -0
  28. package/dist/src/thread-bindings.js +214 -0
  29. package/dist/src/tools/call-phone-tool.js +2 -18
  30. package/dist/src/tools/create-alarm-tool.js +3 -7
  31. package/dist/src/tools/device-tool-map.d.ts +4 -0
  32. package/dist/src/tools/device-tool-map.js +35 -0
  33. package/dist/src/tools/find-pc-devices-tool.d.ts +5 -0
  34. package/dist/src/tools/find-pc-devices-tool.js +98 -0
  35. package/dist/src/tools/image-reading-tool.js +3 -3
  36. package/dist/src/tools/modify-alarm-tool.js +3 -7
  37. package/dist/src/tools/save-file-to-phone-tool.d.ts +5 -0
  38. package/dist/src/tools/save-file-to-phone-tool.js +170 -0
  39. package/dist/src/tools/save-media-to-gallery-tool.d.ts +5 -0
  40. package/dist/src/tools/save-media-to-gallery-tool.js +178 -0
  41. package/dist/src/tools/send-command-to-car-tool.d.ts +5 -0
  42. package/dist/src/tools/send-command-to-car-tool.js +85 -0
  43. package/dist/src/tools/send-file-to-user-tool.js +1 -0
  44. package/dist/src/tools/session-manager.d.ts +1 -0
  45. package/dist/src/tools/timestamp-to-utc8-tool.d.ts +12 -0
  46. package/dist/src/tools/timestamp-to-utc8-tool.js +104 -0
  47. package/dist/src/tools/upload-file-tool.js +2 -2
  48. package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +4 -0
  49. package/dist/src/tools/xiaoyi-add-collection-tool.js +188 -0
  50. package/dist/src/tools/xiaoyi-collection-tool.js +42 -7
  51. package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +4 -0
  52. package/dist/src/tools/xiaoyi-delete-collection-tool.js +163 -0
  53. package/dist/src/utils/runtime-manager.d.ts +7 -0
  54. package/dist/src/utils/runtime-manager.js +42 -0
  55. package/dist/src/websocket.js +15 -9
  56. package/openclaw.plugin.json +1 -0
  57. package/package.json +3 -4
  58. package/dist/src/tools/search-photo-tool.d.ts +0 -9
  59. package/dist/src/tools/search-photo-tool.js +0 -270
  60. package/dist/src/utils/session.d.ts +0 -34
  61. package/dist/src/utils/session.js +0 -50
@@ -1,26 +1,54 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { sendCommand } from "../formatter.js";
3
3
  import { getCurrentSessionContext } from "./session-manager.js";
4
+ /**
5
+ * Duck-typed ToolInputError: openclaw 按 .name 字段匹配,不用 instanceof。
6
+ * 抛出此错误会让 openclaw 返回 HTTP 400 而非 500,
7
+ * LLM 会将其识别为参数错误而非瞬时故障,不会触发重试。
8
+ */
9
+ class ToolInputError extends Error {
10
+ status = 400;
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "ToolInputError";
14
+ }
15
+ }
4
16
  /**
5
17
  * XY collection tool - retrieves user's collection data from XiaoYi.
6
18
  * Returns personalized knowledge data saved in user's collection.
7
19
  */
8
20
  export const xiaoyiCollectionTool = {
9
- name: "xiaoyi_collection",
21
+ name: "query_collection",
10
22
  label: "XiaoYi Collection",
11
- description: "检索用户在小艺收藏中记下来的公共知识数据,可以给用户提供个性化体验。当用户语料中涉及从我的小艺收藏或者查看我的收藏或者我xx时候收藏的xxx帮我看一下这种类型的语料的时候需要使用此工具。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
23
+ description: `检索用户在小艺收藏中记下来的公共知识数据,本技能支持查询用户收藏的公共知识数据,也可以根据特定语义化描述进行特定内容的检索,通过参数进行控制。本技能返回结果中,linkTitle是收藏内容的标题,description是对收藏内容的总结,label是收藏内容的标签,linkUrl是可以直接访问的原始内容链接。如果你认为某条数据对用户交互有用,可以通过linkUrl抓取更加丰富的原始数据。
24
+ 注意:
25
+ a. 操作超时时间为60秒,请勿重复调用此工具
26
+ b. 如果遇到各类调用失败场景,最多只能重试一次,不可以重复调用多次。
27
+ c. 调用工具前需认真检查调用参数是否满足工具要求
28
+
29
+ 回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。
30
+ `,
12
31
  parameters: {
13
32
  type: "object",
14
33
  properties: {
15
34
  queryAll: {
16
35
  type: "string",
17
- description: "描述是否需要查询用户所有收藏数据,默认为true",
18
- default: "true",
36
+ description: "非必填参数,描述是否需要查询用户所有收藏数据。如果填入true则表示获取用户所有公共知识数据,其他参数无效。",
37
+ },
38
+ query: {
39
+ type: "string",
40
+ description: "非必填参数,queryAll不填或者为false则必填。用户的查询条件,可按照用户query进行检索。",
19
41
  },
20
42
  },
21
43
  required: [],
22
44
  },
23
45
  async execute(toolCallId, params) {
46
+ // Validate parameters
47
+ const queryAll = params.queryAll;
48
+ const query = params.query;
49
+ if (queryAll !== "true" && (!query || typeof query !== "string")) {
50
+ throw new ToolInputError("queryAll不为true时,query参数必填");
51
+ }
24
52
  // Get session context
25
53
  const sessionContext = getCurrentSessionContext();
26
54
  if (!sessionContext) {
@@ -29,6 +57,15 @@ export const xiaoyiCollectionTool = {
29
57
  const { config, sessionId, taskId, messageId } = sessionContext;
30
58
  // Get WebSocket manager
31
59
  const wsManager = getXYWebSocketManager(config);
60
+ // Build intentParam
61
+ const intentParam = {};
62
+ if (queryAll === "true") {
63
+ intentParam.queryAll = "true";
64
+ }
65
+ else {
66
+ intentParam.queryAll = "false";
67
+ intentParam.query = query;
68
+ }
32
69
  // Build QueryCollection command
33
70
  const command = {
34
71
  header: {
@@ -45,9 +82,7 @@ export const xiaoyiCollectionTool = {
45
82
  actionResponse: true,
46
83
  appType: "OHOS_APP",
47
84
  timeOut: 5,
48
- intentParam: {
49
- queryAll: params.queryAll || "true",
50
- },
85
+ intentParam,
51
86
  permissionId: [],
52
87
  achieveType: "INTENT",
53
88
  },
@@ -0,0 +1,4 @@
1
+ /**
2
+ * XY delete collection tool - deletes data from user's XiaoYi collection.
3
+ */
4
+ export declare const xiaoyiDeleteCollectionTool: any;
@@ -0,0 +1,163 @@
1
+ import { getXYWebSocketManager } from "../client.js";
2
+ import { sendCommand } from "../formatter.js";
3
+ import { getCurrentSessionContext } from "./session-manager.js";
4
+ /**
5
+ * Duck-typed ToolInputError: openclaw 按 .name 字段匹配,不用 instanceof。
6
+ * 抛出此错误会让 openclaw 返回 HTTP 400 而非 500,
7
+ * LLM 会将其识别为参数错误而非瞬时故障,不会触发重试。
8
+ */
9
+ class ToolInputError extends Error {
10
+ status = 400;
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "ToolInputError";
14
+ }
15
+ }
16
+ /**
17
+ * XY delete collection tool - deletes data from user's XiaoYi collection.
18
+ */
19
+ export const xiaoyiDeleteCollectionTool = {
20
+ name: "delete_collection",
21
+ label: "Delete XiaoYi Collection",
22
+ description: `从小艺收藏中删除之前已保存的公共知识数据。任何用户希望删除已保存到个人知识库的数据都可以调用本技能。如果用户想更新之前的收藏数据,需要先query获取itemId然后再delete,最后执行Add,按照这个步骤完成收藏数据更新。
23
+ 注意:
24
+ a. 操作超时时间为60秒,请勿重复调用此工具
25
+ b. 如果遇到各类调用失败场景,最多只能重试一次,不可以重复调用多次。
26
+ c. 调用工具前需认真检查调用参数是否满足工具要求
27
+
28
+ 回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。
29
+ `,
30
+ parameters: {
31
+ type: "object",
32
+ properties: {
33
+ itemIds: {
34
+ // 不指定 type,允许传入数组或 JSON 字符串
35
+ // 具体的类型验证和转换在 execute 函数内部进行
36
+ description: "准备删除的数据的itemId合集。itemId可以由用户指定,也可以从之前检索回来的收藏数据项的itemId字段获取。",
37
+ },
38
+ },
39
+ required: ["itemIds"],
40
+ },
41
+ async execute(toolCallId, params) {
42
+ // ===== 参数规范化:兼容数组和 JSON 字符串 =====
43
+ let itemIds = null;
44
+ if (!params.itemIds) {
45
+ throw new ToolInputError("缺少必填参数: itemIds");
46
+ }
47
+ // 情况1: 已经是数组
48
+ if (Array.isArray(params.itemIds)) {
49
+ itemIds = params.itemIds;
50
+ }
51
+ // 情况2: 是字符串,尝试解析为 JSON 数组
52
+ else if (typeof params.itemIds === 'string') {
53
+ try {
54
+ const parsed = JSON.parse(params.itemIds);
55
+ if (Array.isArray(parsed)) {
56
+ itemIds = parsed;
57
+ }
58
+ else {
59
+ throw new ToolInputError("itemIds must be an array or a JSON string representing an array");
60
+ }
61
+ }
62
+ catch (parseError) {
63
+ if (parseError instanceof ToolInputError)
64
+ throw parseError;
65
+ throw new ToolInputError(`itemIds must be a valid JSON array string. Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
66
+ }
67
+ }
68
+ // 情况3: 其他类型,报错
69
+ else {
70
+ throw new ToolInputError(`itemIds must be an array or a JSON string, got ${typeof params.itemIds}`);
71
+ }
72
+ // 验证数组非空
73
+ if (!itemIds || itemIds.length === 0) {
74
+ throw new ToolInputError("itemIds array cannot be empty");
75
+ }
76
+ // Get session context
77
+ const sessionContext = getCurrentSessionContext();
78
+ if (!sessionContext) {
79
+ throw new Error("No active XY session found. DeleteCollection tool can only be used during an active conversation.");
80
+ }
81
+ const { config, sessionId, taskId, messageId } = sessionContext;
82
+ // Get WebSocket manager
83
+ const wsManager = getXYWebSocketManager(config);
84
+ // Build DeleteCollection command
85
+ const command = {
86
+ header: {
87
+ namespace: "Common",
88
+ name: "Action",
89
+ },
90
+ payload: {
91
+ cardParam: {},
92
+ executeParam: {
93
+ executeMode: "background",
94
+ intentName: "DeleteCollection",
95
+ bundleName: "com.huawei.hmos.vassistant",
96
+ needUnlock: true,
97
+ actionResponse: true,
98
+ appType: "OHOS_APP",
99
+ timeOut: 5,
100
+ intentParam: {
101
+ itemIds,
102
+ },
103
+ permissionId: [],
104
+ achieveType: "INTENT",
105
+ },
106
+ responses: [
107
+ {
108
+ resultCode: "",
109
+ displayText: "",
110
+ ttsText: "",
111
+ },
112
+ ],
113
+ needUploadResult: true,
114
+ noHalfPage: false,
115
+ pageControlRelated: false,
116
+ },
117
+ };
118
+ // Send command and wait for response (60 second timeout)
119
+ return new Promise((resolve, reject) => {
120
+ const timeout = setTimeout(() => {
121
+ wsManager.off("data-event", handler);
122
+ reject(new Error("删除小艺收藏超时(60秒)"));
123
+ }, 60000);
124
+ // Listen for data events from WebSocket
125
+ const handler = (event) => {
126
+ if (event.intentName === "DeleteCollection") {
127
+ clearTimeout(timeout);
128
+ wsManager.off("data-event", handler);
129
+ if (event.status === "success" && event.outputs) {
130
+ resolve({
131
+ content: [
132
+ {
133
+ type: "text",
134
+ text: JSON.stringify(event.outputs),
135
+ }
136
+ ]
137
+ });
138
+ }
139
+ else {
140
+ reject(new Error(`删除小艺收藏失败: ${event.status}`));
141
+ }
142
+ }
143
+ };
144
+ // Register event handler
145
+ wsManager.on("data-event", handler);
146
+ // Send the command
147
+ sendCommand({
148
+ config,
149
+ sessionId,
150
+ taskId,
151
+ messageId,
152
+ command,
153
+ })
154
+ .then(() => {
155
+ })
156
+ .catch((error) => {
157
+ clearTimeout(timeout);
158
+ wsManager.off("data-event", handler);
159
+ reject(error);
160
+ });
161
+ });
162
+ },
163
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 保存 runtime 信息到 .xiaoyiruntime 文件
3
+ * @param webSocketSessionId - WebSocket 层级的 sessionId (SESSION_ID)
4
+ * @param conversationId - param 里的 sessionId (CONVERSATION_ID)
5
+ * @param taskId - 任务 ID (param.id)
6
+ */
7
+ export declare function saveRuntimeInfo(webSocketSessionId: string, conversationId: string, taskId: string): Promise<void>;
@@ -0,0 +1,42 @@
1
+ // xiaoyi runtime 持久化管理器
2
+ import { promises as fs } from "fs";
3
+ import * as path from "path";
4
+ import { logger } from "./logger.js";
5
+ const RUNTIME_FILE = "/home/sandbox/.openclaw/.xiaoyiruntime";
6
+ /**
7
+ * 确保目录存在
8
+ */
9
+ async function ensureDirectoryExists(filePath) {
10
+ const dir = path.dirname(filePath);
11
+ try {
12
+ await fs.mkdir(dir, { recursive: true });
13
+ }
14
+ catch (error) {
15
+ logger.error(`[RuntimeManager] Failed to create directory ${dir}:`, error);
16
+ }
17
+ }
18
+ /**
19
+ * 保存 runtime 信息到 .xiaoyiruntime 文件
20
+ * @param webSocketSessionId - WebSocket 层级的 sessionId (SESSION_ID)
21
+ * @param conversationId - param 里的 sessionId (CONVERSATION_ID)
22
+ * @param taskId - 任务 ID (param.id)
23
+ */
24
+ export async function saveRuntimeInfo(webSocketSessionId, conversationId, taskId) {
25
+ if (!webSocketSessionId || !conversationId || !taskId) {
26
+ logger.warn(`[RuntimeManager] Invalid params: SESSION_ID=${webSocketSessionId}, CONVERSATION_ID=${conversationId}, TASK_ID=${taskId}`);
27
+ return;
28
+ }
29
+ try {
30
+ await ensureDirectoryExists(RUNTIME_FILE);
31
+ const content = `SESSION_ID=${webSocketSessionId}\nCONVERSATION_ID=${conversationId}\nTASK_ID=${taskId}\n`;
32
+ await fs.writeFile(RUNTIME_FILE, content, "utf-8");
33
+ logger.log(`[RuntimeManager] ✅ Saved runtime info to .xiaoyiruntime`);
34
+ logger.log(`[RuntimeManager] - SESSION_ID: ${webSocketSessionId}`);
35
+ logger.log(`[RuntimeManager] - CONVERSATION_ID: ${conversationId}`);
36
+ logger.log(`[RuntimeManager] - TASK_ID: ${taskId}`);
37
+ }
38
+ catch (error) {
39
+ logger.error(`[RuntimeManager] Failed to save runtime info:`, error);
40
+ // 不抛出异常,避免影响主流程
41
+ }
42
+ }
@@ -318,30 +318,37 @@ export class XYWebSocketManager extends EventEmitter {
318
318
  * Handle incoming message from server.
319
319
  */
320
320
  handleMessage(data) {
321
- console.log("[WEBSOCKET-HANDLE] >>>>>>> Receiving message... <<<<<<<");
322
321
  try {
323
322
  const messageStr = data.toString();
324
- console.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} bytes`);
323
+ console.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} characters`);
325
324
  const parsed = JSON.parse(messageStr);
326
325
  // 提取并打印消息内容(只显示 text,data 只打印提示)
327
326
  const parts = parsed.params?.message?.parts;
328
327
  if (parts && Array.isArray(parts) && parts.length > 0) {
329
328
  const textParts = parts.filter((p) => p?.kind === "text");
330
329
  const dataParts = parts.filter((p) => p?.kind === "data");
331
- // 打印 text 内容
330
+ // 打印 text 内容(隐藏敏感信息)
332
331
  if (textParts.length > 0) {
333
332
  const textContents = textParts
334
333
  .map((p) => p?.text || "")
335
334
  .filter((text) => text.length > 0)
336
335
  .join(" ");
337
336
  if (textContents.length > 0) {
338
- console.log("[WS-RECV] Text:", textContents);
337
+ // 隐藏中间内容,只保留前后各5个字符
338
+ let maskedText;
339
+ if (textContents.length <= 8) {
340
+ // 如果长度 <= 8,显示前2个 + *** + 后2个
341
+ maskedText = textContents.length >= 4
342
+ ? `${textContents.slice(0, 2)}***${textContents.slice(-2)}`
343
+ : `${textContents.slice(0, 1)}***${textContents.slice(-1)}`;
344
+ }
345
+ else {
346
+ // 如果长度 > 8,显示前5个 + *** + 后5个
347
+ maskedText = `${textContents.slice(0, 5)}***${textContents.slice(-5)}`;
348
+ }
349
+ console.log("[WS-RECV] Text:", maskedText);
339
350
  }
340
351
  }
341
- // 打印 data 提示
342
- if (dataParts.length > 0) {
343
- console.log("[WS-RECV] Data: received data message(s)");
344
- }
345
352
  }
346
353
  // Check if message is in direct A2A JSON-RPC format (server push)
347
354
  if (parsed.jsonrpc === "2.0") {
@@ -392,7 +399,6 @@ export class XYWebSocketManager extends EventEmitter {
392
399
  return;
393
400
  }
394
401
  // Emit message event for non-data-only messages
395
- console.log("[XY] *** EMITTING message event (Direct A2A path) ***");
396
402
  this.emit("message", a2aRequest, sessionId);
397
403
  return;
398
404
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "id": "xiaoyi-channel",
3
3
  "channels": ["xiaoyi-channel"],
4
+ "providers": ["xiaoyiprovider"],
4
5
  "skills": [],
5
6
  "configSchema": {
6
7
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.52-beta",
3
+ "version": "0.0.52-next",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,7 +49,7 @@
49
49
  }
50
50
  },
51
51
  "peerDependencies": {
52
- "openclaw": ">=2026.3.1"
52
+ "openclaw": ">=2026.3.24"
53
53
  },
54
54
  "peerDependenciesMeta": {
55
55
  "openclaw": {
@@ -59,13 +59,12 @@
59
59
  "dependencies": {
60
60
  "ws": "^8.14.2",
61
61
  "uuid": "^9.0.0",
62
- "node-fetch": "^2.7.0"
62
+ "node-fetch": "^3.3.2"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/ws": "^8.5.8",
66
66
  "@types/uuid": "^9.0.5",
67
67
  "@types/node": "^20.8.0",
68
- "@types/node-fetch": "^2.6.2",
69
68
  "typescript": "^5.9.2"
70
69
  }
71
70
  }
@@ -1,9 +0,0 @@
1
- /**
2
- * XY search photo tool - searches photos in user's gallery.
3
- * Returns publicly accessible URLs of matching photos based on query description.
4
- *
5
- * This tool performs a two-step operation:
6
- * 1. Search for photos using query description
7
- * 2. Upload found photos to get publicly accessible URLs
8
- */
9
- export declare const searchPhotoTool: any;