@ynhcj/xiaoyi-channel 0.0.26-beta → 0.0.26-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.
- package/dist/index.d.ts +0 -2
- package/dist/index.js +48 -2
- package/dist/src/bot.js +81 -28
- package/dist/src/channel.js +33 -7
- package/dist/src/client.js +11 -27
- package/dist/src/config.js +2 -2
- package/dist/src/cspl/call-api.d.ts +3 -0
- package/dist/src/cspl/call-api.js +107 -0
- package/dist/src/cspl/config.d.ts +19 -0
- package/dist/src/cspl/config.js +50 -0
- package/dist/src/cspl/constants.d.ts +43 -0
- package/dist/src/cspl/constants.js +22 -0
- package/dist/src/cspl/utils.d.ts +10 -0
- package/dist/src/cspl/utils.js +57 -0
- package/dist/src/file-upload.d.ts +5 -0
- package/dist/src/file-upload.js +92 -0
- package/dist/src/formatter.d.ts +14 -0
- package/dist/src/formatter.js +49 -25
- package/dist/src/heartbeat.js +0 -4
- package/dist/src/monitor.js +12 -0
- package/dist/src/onboarding.d.ts +3 -4
- package/dist/src/onboarding.js +2 -2
- package/dist/src/outbound.d.ts +2 -1
- package/dist/src/outbound.js +52 -6
- package/dist/src/parser.d.ts +7 -0
- package/dist/src/parser.js +22 -0
- package/dist/src/push.d.ts +8 -1
- package/dist/src/push.js +30 -22
- package/dist/src/reply-dispatcher.js +39 -5
- package/dist/src/steer-injector.d.ts +16 -0
- package/dist/src/steer-injector.js +74 -0
- package/dist/src/thread-bindings.d.ts +54 -0
- package/dist/src/thread-bindings.js +214 -0
- package/dist/src/tools/calendar-tool.js +7 -40
- package/dist/src/tools/call-phone-tool.d.ts +5 -0
- package/dist/src/tools/call-phone-tool.js +142 -0
- package/dist/src/tools/create-alarm-tool.d.ts +7 -0
- package/dist/src/tools/create-alarm-tool.js +349 -0
- package/dist/src/tools/delete-alarm-tool.d.ts +11 -0
- package/dist/src/tools/delete-alarm-tool.js +174 -0
- package/dist/src/tools/image-reading-tool.d.ts +5 -0
- package/dist/src/tools/image-reading-tool.js +328 -0
- package/dist/src/tools/location-tool.js +8 -42
- package/dist/src/tools/modify-alarm-tool.d.ts +9 -0
- package/dist/src/tools/modify-alarm-tool.js +365 -0
- package/dist/src/tools/modify-note-tool.js +5 -43
- package/dist/src/tools/note-tool.js +33 -24
- package/dist/src/tools/search-alarm-tool.d.ts +8 -0
- package/dist/src/tools/search-alarm-tool.js +285 -0
- package/dist/src/tools/search-calendar-tool.js +10 -84
- package/dist/src/tools/search-contact-tool.js +4 -57
- package/dist/src/tools/search-file-tool.d.ts +5 -0
- package/dist/src/tools/search-file-tool.js +128 -0
- package/dist/src/tools/search-message-tool.d.ts +5 -0
- package/dist/src/tools/search-message-tool.js +116 -0
- package/dist/src/tools/search-note-tool.js +6 -24
- package/dist/src/tools/search-photo-gallery-tool.js +40 -61
- package/dist/src/tools/send-file-to-user-tool.d.ts +5 -0
- package/dist/src/tools/send-file-to-user-tool.js +279 -0
- package/dist/src/tools/send-message-tool.d.ts +5 -0
- package/dist/src/tools/send-message-tool.js +138 -0
- package/dist/src/tools/session-manager.d.ts +15 -0
- package/dist/src/tools/session-manager.js +43 -31
- package/dist/src/tools/upload-file-tool.d.ts +13 -0
- package/dist/src/tools/upload-file-tool.js +216 -0
- package/dist/src/tools/upload-photo-tool.js +2 -44
- package/dist/src/tools/view-push-result-tool.d.ts +5 -0
- package/dist/src/tools/view-push-result-tool.js +107 -0
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +5 -0
- package/dist/src/tools/xiaoyi-collection-tool.js +112 -0
- package/dist/src/tools/xiaoyi-gui-tool.js +2 -36
- package/dist/src/trigger-handler.d.ts +22 -0
- package/dist/src/trigger-handler.js +59 -0
- package/dist/src/types.d.ts +1 -8
- package/dist/src/types.js +4 -0
- package/dist/src/utils/pushdata-manager.d.ts +28 -0
- package/dist/src/utils/pushdata-manager.js +171 -0
- package/dist/src/utils/pushid-manager.d.ts +12 -0
- package/dist/src/utils/pushid-manager.js +105 -0
- package/dist/src/websocket.d.ts +25 -31
- package/dist/src/websocket.js +233 -271
- package/package.json +2 -2
- package/dist/src/tools/search-photo-tool.d.ts +0 -9
- package/dist/src/tools/search-photo-tool.js +0 -270
- package/dist/src/utils/session.d.ts +0 -34
- package/dist/src/utils/session.js +0 -50
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
|
+
/**
|
|
5
|
+
* XY search file tool - searches files on user's device file system.
|
|
6
|
+
* Returns matching files based on keyword search in file name or content.
|
|
7
|
+
*/
|
|
8
|
+
export const searchFileTool = {
|
|
9
|
+
name: "search_file",
|
|
10
|
+
label: "Search File",
|
|
11
|
+
description: `搜索手机文件系统的文件。
|
|
12
|
+
|
|
13
|
+
【重要】使用约束:此工具仅在用户显著说明要从手机搜索时才执行,例如:
|
|
14
|
+
- "从我手机里面搜索xxxx"
|
|
15
|
+
- "从手机文件系统找一下xxxx"
|
|
16
|
+
- "在手机上查找文件xxxx"
|
|
17
|
+
- "搜索手机里的文件"
|
|
18
|
+
|
|
19
|
+
如果用户没有明确说明从手机搜索(如仅说"搜索文件"、"找一下xxxx"),应默认从 openclaw 本地的文件系统查询,不要调用此工具。
|
|
20
|
+
|
|
21
|
+
功能说明:根据关键词搜索文件名称或内容,返回匹配的文件列表(包括文件名、路径、大小、修改时间等信息)。
|
|
22
|
+
|
|
23
|
+
注意事项:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。`,
|
|
24
|
+
parameters: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
query: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "搜索关键词,用于匹配文件名称、后缀名或文件内容",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
required: ["query"],
|
|
33
|
+
},
|
|
34
|
+
async execute(toolCallId, params) {
|
|
35
|
+
// Validate query parameter
|
|
36
|
+
if (!params.query || typeof params.query !== "string" || params.query.trim() === "") {
|
|
37
|
+
throw new Error("Missing required parameter: query must be a non-empty string");
|
|
38
|
+
}
|
|
39
|
+
// Get session context
|
|
40
|
+
const sessionContext = getCurrentSessionContext();
|
|
41
|
+
if (!sessionContext) {
|
|
42
|
+
throw new Error("No active XY session found. Search file tool can only be used during an active conversation.");
|
|
43
|
+
}
|
|
44
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
45
|
+
// Get WebSocket manager
|
|
46
|
+
const wsManager = getXYWebSocketManager(config);
|
|
47
|
+
// Build SearchFile command
|
|
48
|
+
const command = {
|
|
49
|
+
header: {
|
|
50
|
+
namespace: "Common",
|
|
51
|
+
name: "Action",
|
|
52
|
+
},
|
|
53
|
+
payload: {
|
|
54
|
+
cardParam: {},
|
|
55
|
+
executeParam: {
|
|
56
|
+
executeMode: "background",
|
|
57
|
+
intentName: "SearchFile",
|
|
58
|
+
bundleName: "com.huawei.hmos.aidispatchservice",
|
|
59
|
+
needUnlock: true,
|
|
60
|
+
actionResponse: true,
|
|
61
|
+
appType: "OHOS_APP",
|
|
62
|
+
timeOut: 5,
|
|
63
|
+
intentParam: {
|
|
64
|
+
query: params.query.trim(),
|
|
65
|
+
},
|
|
66
|
+
permissionId: [],
|
|
67
|
+
achieveType: "INTENT",
|
|
68
|
+
},
|
|
69
|
+
responses: [
|
|
70
|
+
{
|
|
71
|
+
resultCode: "",
|
|
72
|
+
displayText: "",
|
|
73
|
+
ttsText: "",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
needUploadResult: true,
|
|
77
|
+
noHalfPage: false,
|
|
78
|
+
pageControlRelated: false,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
// Send command and wait for response (60 second timeout)
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const timeout = setTimeout(() => {
|
|
84
|
+
wsManager.off("data-event", handler);
|
|
85
|
+
reject(new Error("搜索文件超时(60秒)"));
|
|
86
|
+
}, 60000);
|
|
87
|
+
// Listen for data events from WebSocket
|
|
88
|
+
const handler = (event) => {
|
|
89
|
+
if (event.intentName === "SearchFile") {
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
wsManager.off("data-event", handler);
|
|
92
|
+
if (event.status === "success" && event.outputs) {
|
|
93
|
+
// 成功,直接返回完整的 event.outputs JSON 字符串
|
|
94
|
+
resolve({
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: JSON.stringify(event.outputs),
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const errorDetail = event.outputs ? JSON.stringify(event.outputs) : event.status;
|
|
105
|
+
reject(new Error(`搜索文件失败: ${errorDetail}`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
// Register event handler
|
|
110
|
+
wsManager.on("data-event", handler);
|
|
111
|
+
// Send the command
|
|
112
|
+
sendCommand({
|
|
113
|
+
config,
|
|
114
|
+
sessionId,
|
|
115
|
+
taskId,
|
|
116
|
+
messageId,
|
|
117
|
+
command,
|
|
118
|
+
})
|
|
119
|
+
.then(() => {
|
|
120
|
+
})
|
|
121
|
+
.catch((error) => {
|
|
122
|
+
clearTimeout(timeout);
|
|
123
|
+
wsManager.off("data-event", handler);
|
|
124
|
+
reject(error);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
|
+
/**
|
|
5
|
+
* XY search message tool - searches SMS messages on user's device.
|
|
6
|
+
* Returns matching messages based on content keyword search.
|
|
7
|
+
*/
|
|
8
|
+
export const searchMessageTool = {
|
|
9
|
+
name: "search_message",
|
|
10
|
+
label: "Search Message",
|
|
11
|
+
description: "搜索手机短信。根据关键词搜索短信内容。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
|
|
12
|
+
parameters: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
content: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "搜索关键词,用于在短信内容中进行匹配",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
required: ["content"],
|
|
21
|
+
},
|
|
22
|
+
async execute(toolCallId, params) {
|
|
23
|
+
// Validate content parameter
|
|
24
|
+
if (!params.content || typeof params.content !== "string" || params.content.trim() === "") {
|
|
25
|
+
throw new Error("Missing required parameter: content must be a non-empty string");
|
|
26
|
+
}
|
|
27
|
+
// Get session context
|
|
28
|
+
const sessionContext = getCurrentSessionContext();
|
|
29
|
+
if (!sessionContext) {
|
|
30
|
+
throw new Error("No active XY session found. Search message tool can only be used during an active conversation.");
|
|
31
|
+
}
|
|
32
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
33
|
+
// Get WebSocket manager
|
|
34
|
+
const wsManager = getXYWebSocketManager(config);
|
|
35
|
+
// Build SearchMessage command
|
|
36
|
+
const command = {
|
|
37
|
+
header: {
|
|
38
|
+
namespace: "Common",
|
|
39
|
+
name: "Action",
|
|
40
|
+
},
|
|
41
|
+
payload: {
|
|
42
|
+
cardParam: {},
|
|
43
|
+
executeParam: {
|
|
44
|
+
executeMode: "background",
|
|
45
|
+
intentName: "SearchMessage",
|
|
46
|
+
bundleName: "com.huawei.hmos.aidispatchservice",
|
|
47
|
+
needUnlock: true,
|
|
48
|
+
actionResponse: true,
|
|
49
|
+
appType: "OHOS_APP",
|
|
50
|
+
timeOut: 5,
|
|
51
|
+
intentParam: {
|
|
52
|
+
content: params.content.trim(),
|
|
53
|
+
},
|
|
54
|
+
permissionId: [],
|
|
55
|
+
achieveType: "INTENT",
|
|
56
|
+
},
|
|
57
|
+
responses: [
|
|
58
|
+
{
|
|
59
|
+
resultCode: "",
|
|
60
|
+
displayText: "",
|
|
61
|
+
ttsText: "",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
needUploadResult: true,
|
|
65
|
+
noHalfPage: false,
|
|
66
|
+
pageControlRelated: false,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
// Send command and wait for response (60 second timeout)
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const timeout = setTimeout(() => {
|
|
72
|
+
wsManager.off("data-event", handler);
|
|
73
|
+
reject(new Error("搜索短信超时(60秒)"));
|
|
74
|
+
}, 60000);
|
|
75
|
+
// Listen for data events from WebSocket
|
|
76
|
+
const handler = (event) => {
|
|
77
|
+
if (event.intentName === "SearchMessage") {
|
|
78
|
+
clearTimeout(timeout);
|
|
79
|
+
wsManager.off("data-event", handler);
|
|
80
|
+
if (event.status === "success" && event.outputs) {
|
|
81
|
+
// 成功,直接返回完整的 event.outputs JSON 字符串
|
|
82
|
+
resolve({
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: JSON.stringify(event.outputs),
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const errorDetail = event.outputs ? JSON.stringify(event.outputs) : event.status;
|
|
93
|
+
reject(new Error(`搜索短信失败: ${errorDetail}`));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
// Register event handler
|
|
98
|
+
wsManager.on("data-event", handler);
|
|
99
|
+
// Send the command
|
|
100
|
+
sendCommand({
|
|
101
|
+
config,
|
|
102
|
+
sessionId,
|
|
103
|
+
taskId,
|
|
104
|
+
messageId,
|
|
105
|
+
command,
|
|
106
|
+
})
|
|
107
|
+
.then(() => {
|
|
108
|
+
})
|
|
109
|
+
.catch((error) => {
|
|
110
|
+
clearTimeout(timeout);
|
|
111
|
+
wsManager.off("data-event", handler);
|
|
112
|
+
reject(error);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getXYWebSocketManager } from "../client.js";
|
|
2
2
|
import { sendCommand } from "../formatter.js";
|
|
3
|
-
import {
|
|
4
|
-
import { logger } from "../utils/logger.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
5
4
|
/**
|
|
6
5
|
* XY search note tool - searches notes on user's device.
|
|
7
6
|
* Returns matching notes based on query string.
|
|
@@ -9,7 +8,7 @@ import { logger } from "../utils/logger.js";
|
|
|
9
8
|
export const searchNoteTool = {
|
|
10
9
|
name: "search_notes",
|
|
11
10
|
label: "Search Notes",
|
|
12
|
-
description: "搜索用户设备上的备忘录内容。根据关键词在备忘录的标题、内容和附件名称中进行检索。注意:操作超时时间为60
|
|
11
|
+
description: "搜索用户设备上的备忘录内容。根据关键词在备忘录的标题、内容和附件名称中进行检索。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。",
|
|
13
12
|
parameters: {
|
|
14
13
|
type: "object",
|
|
15
14
|
properties: {
|
|
@@ -21,13 +20,12 @@ export const searchNoteTool = {
|
|
|
21
20
|
required: ["query"],
|
|
22
21
|
},
|
|
23
22
|
async execute(toolCallId, params) {
|
|
24
|
-
logger.debug("Executing search note tool, toolCallId:", toolCallId);
|
|
25
23
|
// Validate parameters
|
|
26
24
|
if (!params.query) {
|
|
27
25
|
throw new Error("Missing required parameter: query is required");
|
|
28
26
|
}
|
|
29
27
|
// Get session context
|
|
30
|
-
const sessionContext =
|
|
28
|
+
const sessionContext = getCurrentSessionContext();
|
|
31
29
|
if (!sessionContext) {
|
|
32
30
|
throw new Error("No active XY session found. Search note tool can only be used during an active conversation.");
|
|
33
31
|
}
|
|
@@ -75,33 +73,16 @@ export const searchNoteTool = {
|
|
|
75
73
|
}, 60000);
|
|
76
74
|
// Listen for data events from WebSocket
|
|
77
75
|
const handler = (event) => {
|
|
78
|
-
logger.debug("Received data event:", event);
|
|
79
76
|
if (event.intentName === "SearchNote") {
|
|
80
77
|
clearTimeout(timeout);
|
|
81
78
|
wsManager.off("data-event", handler);
|
|
82
79
|
if (event.status === "success" && event.outputs) {
|
|
83
|
-
|
|
84
|
-
const items = result?.items || [];
|
|
85
|
-
logger.log(`Notes found: ${items.length} results for query "${params.query}"`);
|
|
80
|
+
// 成功,直接返回完整的 event.outputs JSON 字符串
|
|
86
81
|
resolve({
|
|
87
82
|
content: [
|
|
88
83
|
{
|
|
89
84
|
type: "text",
|
|
90
|
-
text: JSON.stringify(
|
|
91
|
-
success: true,
|
|
92
|
-
query: params.query,
|
|
93
|
-
totalResults: items.length,
|
|
94
|
-
notes: items.map((item) => ({
|
|
95
|
-
entityId: item.entityId,
|
|
96
|
-
entityName: item.entityName,
|
|
97
|
-
title: item.title?.replace(/<\/?em>/g, ''), // Remove <em> tags
|
|
98
|
-
content: item.content,
|
|
99
|
-
createdDate: item.createdDate,
|
|
100
|
-
modifiedDate: item.modifiedDate,
|
|
101
|
-
})),
|
|
102
|
-
indexName: result?.indexName,
|
|
103
|
-
code,
|
|
104
|
-
}),
|
|
85
|
+
text: JSON.stringify(event.outputs),
|
|
105
86
|
},
|
|
106
87
|
],
|
|
107
88
|
});
|
|
@@ -120,6 +101,7 @@ export const searchNoteTool = {
|
|
|
120
101
|
taskId,
|
|
121
102
|
messageId,
|
|
122
103
|
command,
|
|
104
|
+
}).then(() => {
|
|
123
105
|
}).catch((error) => {
|
|
124
106
|
clearTimeout(timeout);
|
|
125
107
|
wsManager.off("data-event", handler);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getXYWebSocketManager } from "../client.js";
|
|
2
2
|
import { sendCommand } from "../formatter.js";
|
|
3
|
-
import {
|
|
4
|
-
import { logger } from "../utils/logger.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
5
4
|
/**
|
|
6
5
|
* XY search photo gallery tool - searches photos in user's gallery.
|
|
7
6
|
* Returns local mediaUri strings that can be used with upload_photo tool.
|
|
@@ -13,83 +12,77 @@ export const searchPhotoGalleryTool = {
|
|
|
13
12
|
name: "search_photo_gallery",
|
|
14
13
|
label: "Search Photo Gallery",
|
|
15
14
|
description: `插件功能描述:搜索用户手机图库中的照片
|
|
16
|
-
|
|
15
|
+
|
|
16
|
+
工具使用约束:如果用户说从手机图库中或者从相册中查询xx图片时调用此工具,注意此工具仅支持从本地图库检索,不支持云空间相册检索。
|
|
17
|
+
|
|
17
18
|
工具输入输出简介:
|
|
18
19
|
a. 根据图像描述语料检索匹配的照片,返回照片在手机本地的 mediaUri以及thumbnailUri。
|
|
19
20
|
b. 返回的 mediaUri以及thumbnailUri 是本地路径,无法直接下载或访问。如果需要下载、查看、使用或展示照片,请使用 upload_photo 工具将 mediaUri或者thumbnailUri 转换为可访问的公网 URL。
|
|
20
21
|
c. mediaUri代表手机相册中的图片原图路径,图片大小比较大,清晰度比较高
|
|
21
22
|
d. thumbnailUri代表手机相册中的图片缩略图路径,图片大小比较小,清晰度适中,建议在upload_photo 工具的入参中优先使用此路径,不容易引起上传超时等问题
|
|
22
|
-
|
|
23
|
+
|
|
24
|
+
搜索能力边界:
|
|
25
|
+
a. ✅ 支持口语化输入:改写模型会自动提取姓名、种类、地点等实体,可以使用自然语言描述(如"小狗的照片"、"南京拍的风景")
|
|
26
|
+
b. ✅ 支持相册搜索:可以在query中包含相册名称(如"西安之行相册的照片")
|
|
27
|
+
c. ✅ 支持人像搜索:前提是照片有人像tag,且需要口语化描述(如"张三的照片")
|
|
28
|
+
d. ❌ 不支持时间相对词:不支持"最新"、"最旧"、"最早"等表述,需要使用具体时间(如"2024年的照片"而非"去年的照片")
|
|
29
|
+
e. ❌ 不支持多实体查询:不支持"或"逻辑和时间范围(如"南京或上海的照片"、"近三年的照片"),需要拆分成多次独立查询
|
|
30
|
+
f. ❌ 不支持POI逆地理映射:照片的location是门牌号,用真实场地名称可能搜不到
|
|
31
|
+
g. ❌ 不支持收藏感知:无法感知照片是否被收藏
|
|
32
|
+
h. ❌ 不支持细粒度品种:对于动物、植物等的具体品种识别能力有限
|
|
33
|
+
i. ⚠️ POI提取可能不准确:地名可能作为语义搜索条件,可能导致"xx湖"搜到"yy江"或"zz湾"的照片
|
|
34
|
+
|
|
35
|
+
查询优化建议:
|
|
36
|
+
a. 时间查询:将"最新"、"去年"、"近三年"等转换为具体年份(如"2024年"、"2023年到2025年"需拆分成"2023年"、"2024年"、"2025年"三次查询)
|
|
37
|
+
b. 多条件查询:将"或"逻辑拆分成多次查询(如"南京或上海的照片"→先查"南京的照片",再查"上海的照片")
|
|
38
|
+
c. 实体原子化:确保每个query只包含一个原子实体(地点、人名、物品等)
|
|
39
|
+
d. 相册名称:如果知道相册名,直接在query中包含相册名可以提高准确度
|
|
40
|
+
|
|
23
41
|
注意事项:
|
|
24
42
|
a. 只有当用户明确表达从手机相册搜索或者从图库搜索时才执行此工具,如果用户仅表达要搜索xxx图片,并没有说明搜索数据源,则不要贸然调用此插件,可以优先尝试websearch或者询问用户是否要从手机图库中搜索。
|
|
25
43
|
b. 操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。
|
|
44
|
+
c. 如果用户请求包含多个实体或时间范围,需要主动拆分成多次查询并告知用户。
|
|
45
|
+
|
|
46
|
+
回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。
|
|
26
47
|
`,
|
|
27
48
|
parameters: {
|
|
28
49
|
type: "object",
|
|
29
50
|
properties: {
|
|
30
51
|
query: {
|
|
31
52
|
type: "string",
|
|
32
|
-
description:
|
|
53
|
+
description: `图像描述语料,用于检索匹配的照片。支持口语化输入,会自动提取姓名、种类、地点等实体。
|
|
54
|
+
|
|
55
|
+
使用示例:
|
|
56
|
+
- 正确:"小狗的照片"、"南京拍的风景"、"张三的照片"、"西安之行相册的照片"、"2024年的照片"
|
|
57
|
+
- 错误:"最新的照片"(应改为具体年份如"2024年的照片")
|
|
58
|
+
- 错误:"南京或上海的照片"(需拆分成两次查询:"南京的照片" 和 "上海的照片")
|
|
59
|
+
- 错误:"近三年的照片"(需拆分成"2023年的照片"、"2024年的照片"、"2025年的照片")
|
|
60
|
+
|
|
61
|
+
重要:每次查询只能包含一个原子实体(单个地点、单个人名、单个年份等),不支持多实体或"或"逻辑。`,
|
|
33
62
|
},
|
|
34
63
|
},
|
|
35
64
|
required: ["query"],
|
|
36
65
|
},
|
|
37
66
|
async execute(toolCallId, params) {
|
|
38
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 🚀 Starting execution`);
|
|
39
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - toolCallId: ${toolCallId}`);
|
|
40
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - params:`, JSON.stringify(params));
|
|
41
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
42
67
|
// Validate parameters
|
|
43
68
|
if (!params.query) {
|
|
44
|
-
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] ❌ Missing required parameter: query`);
|
|
45
69
|
throw new Error("Missing required parameter: query is required");
|
|
46
70
|
}
|
|
47
71
|
// Get session context
|
|
48
|
-
|
|
49
|
-
const sessionContext = getLatestSessionContext();
|
|
72
|
+
const sessionContext = getCurrentSessionContext();
|
|
50
73
|
if (!sessionContext) {
|
|
51
|
-
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] ❌ FAILED: No active session found!`);
|
|
52
|
-
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] - toolCallId: ${toolCallId}`);
|
|
53
74
|
throw new Error("No active XY session found. Search photo gallery tool can only be used during an active conversation.");
|
|
54
75
|
}
|
|
55
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] ✅ Session context found`);
|
|
56
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
57
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
58
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
59
76
|
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
60
77
|
// Get WebSocket manager
|
|
61
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 🔌 Getting WebSocket manager...`);
|
|
62
78
|
const wsManager = getXYWebSocketManager(config);
|
|
63
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] ✅ WebSocket manager obtained`);
|
|
64
79
|
// Search for photos
|
|
65
|
-
|
|
66
|
-
const items = await searchPhotos(wsManager, config, sessionId, taskId, messageId, params.query);
|
|
67
|
-
if (!items || items.length === 0) {
|
|
68
|
-
logger.warn(`[SEARCH_PHOTO_GALLERY_TOOL] ⚠️ No photos found for query: ${params.query}`);
|
|
69
|
-
return {
|
|
70
|
-
content: [
|
|
71
|
-
{
|
|
72
|
-
type: "text",
|
|
73
|
-
text: JSON.stringify({
|
|
74
|
-
items: [],
|
|
75
|
-
count: 0,
|
|
76
|
-
message: "未找到匹配的照片"
|
|
77
|
-
}),
|
|
78
|
-
},
|
|
79
|
-
],
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] ✅ Found ${items.length} photos`);
|
|
83
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - items:`, JSON.stringify(items));
|
|
80
|
+
const outputs = await searchPhotos(wsManager, config, sessionId, taskId, messageId, params.query);
|
|
84
81
|
return {
|
|
85
82
|
content: [
|
|
86
83
|
{
|
|
87
84
|
type: "text",
|
|
88
|
-
text: JSON.stringify(
|
|
89
|
-
items,
|
|
90
|
-
count: items.length,
|
|
91
|
-
message: `找到 ${items.length} 张照片。注意:mediaUri 和 thumbnailUri 是本地路径,无法直接访问。如需下载或查看,请使用 upload_photo 工具。`
|
|
92
|
-
}),
|
|
85
|
+
text: JSON.stringify(outputs),
|
|
93
86
|
},
|
|
94
87
|
],
|
|
95
88
|
};
|
|
@@ -97,10 +90,9 @@ export const searchPhotoGalleryTool = {
|
|
|
97
90
|
};
|
|
98
91
|
/**
|
|
99
92
|
* Search for photos using query description
|
|
100
|
-
* Returns
|
|
93
|
+
* Returns complete event.outputs object
|
|
101
94
|
*/
|
|
102
95
|
async function searchPhotos(wsManager, config, sessionId, taskId, messageId, query) {
|
|
103
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 📦 Building SearchPhotoVideo command...`);
|
|
104
96
|
const command = {
|
|
105
97
|
header: {
|
|
106
98
|
namespace: "Common",
|
|
@@ -136,34 +128,23 @@ async function searchPhotos(wsManager, config, sessionId, taskId, messageId, que
|
|
|
136
128
|
};
|
|
137
129
|
return new Promise((resolve, reject) => {
|
|
138
130
|
const timeout = setTimeout(() => {
|
|
139
|
-
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] ⏰ Timeout: No response for SearchPhotoVideo within 60 seconds`);
|
|
140
131
|
wsManager.off("data-event", handler);
|
|
141
132
|
reject(new Error("搜索照片超时(60秒)"));
|
|
142
133
|
}, 60000);
|
|
143
134
|
const handler = (event) => {
|
|
144
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
145
135
|
if (event.intentName === "SearchPhotoVideo") {
|
|
146
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 🎯 SearchPhotoVideo event received`);
|
|
147
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] - status: ${event.status}`);
|
|
148
136
|
clearTimeout(timeout);
|
|
149
137
|
wsManager.off("data-event", handler);
|
|
150
138
|
if (event.status === "success" && event.outputs) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const items = result?.items || [];
|
|
154
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 📊 Found ${items.length} photo items`);
|
|
155
|
-
resolve(items);
|
|
139
|
+
// 成功,直接返回完整的 event.outputs
|
|
140
|
+
resolve(event.outputs);
|
|
156
141
|
}
|
|
157
142
|
else {
|
|
158
|
-
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] ❌ Photo search failed`);
|
|
159
|
-
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] - status: ${event.status}`);
|
|
160
143
|
reject(new Error(`搜索照片失败: ${event.status}`));
|
|
161
144
|
}
|
|
162
145
|
}
|
|
163
146
|
};
|
|
164
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 📡 Registering data-event handler for SearchPhotoVideo`);
|
|
165
147
|
wsManager.on("data-event", handler);
|
|
166
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] 📤 Sending SearchPhotoVideo command...`);
|
|
167
148
|
sendCommand({
|
|
168
149
|
config,
|
|
169
150
|
sessionId,
|
|
@@ -172,10 +153,8 @@ async function searchPhotos(wsManager, config, sessionId, taskId, messageId, que
|
|
|
172
153
|
command,
|
|
173
154
|
})
|
|
174
155
|
.then(() => {
|
|
175
|
-
logger.log(`[SEARCH_PHOTO_GALLERY_TOOL] ✅ SearchPhotoVideo command sent successfully`);
|
|
176
156
|
})
|
|
177
157
|
.catch((error) => {
|
|
178
|
-
logger.error(`[SEARCH_PHOTO_GALLERY_TOOL] ❌ Failed to send SearchPhotoVideo command:`, error);
|
|
179
158
|
clearTimeout(timeout);
|
|
180
159
|
wsManager.off("data-event", handler);
|
|
181
160
|
reject(error);
|