@ynhcj/xiaoyi-channel 0.0.50-next → 0.0.51-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/src/bot.js +40 -20
- package/dist/src/channel.js +1 -2
- package/dist/src/parser.d.ts +7 -4
- package/dist/src/parser.js +7 -2
- package/dist/src/provider.js +28 -2
- package/dist/src/tools/search-file-tool.js +7 -10
- package/dist/src/tools/upload-file-tool.js +4 -14
- package/dist/src/utils/pushdata-manager.d.ts +5 -0
- package/dist/src/utils/pushdata-manager.js +33 -0
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -8,7 +8,7 @@ import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, se
|
|
|
8
8
|
import { registerSession, unregisterSession, runWithSessionContext } from "./tools/session-manager.js";
|
|
9
9
|
import { configManager } from "./utils/config-manager.js";
|
|
10
10
|
import { addPushId } from "./utils/pushid-manager.js";
|
|
11
|
-
import { getPushDataById } from "./utils/pushdata-manager.js";
|
|
11
|
+
import { getPushDataById, getPushDataAfterTimestamp } from "./utils/pushdata-manager.js";
|
|
12
12
|
import { saveRuntimeInfo } from "./utils/runtime-manager.js";
|
|
13
13
|
import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActiveTask, } from "./task-manager.js";
|
|
14
14
|
/**
|
|
@@ -65,29 +65,49 @@ export async function handleXYMessage(params) {
|
|
|
65
65
|
// 如果消息中包含 Trigger 事件数据,直接返回 pushData 内容,不走正常流程
|
|
66
66
|
const triggerData = extractTriggerData(parsed.parts);
|
|
67
67
|
if (triggerData) {
|
|
68
|
-
log(`[BOT] 📌 Detected Trigger message
|
|
68
|
+
log(`[BOT] 📌 Detected Trigger message`);
|
|
69
69
|
log(`[BOT] - Session ID: ${parsed.sessionId}`);
|
|
70
70
|
log(`[BOT] - Task ID: ${parsed.taskId}`);
|
|
71
71
|
try {
|
|
72
|
-
// 读取 pushData
|
|
73
|
-
const pushDataItem = await getPushDataById(triggerData.pushDataId);
|
|
74
|
-
if (!pushDataItem) {
|
|
75
|
-
error(`[BOT] ❌ pushData not found for ID: ${triggerData.pushDataId}`);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
log(`[BOT] ✅ Found pushData, sending direct response`);
|
|
79
|
-
log(`[BOT] - pushDataId: ${pushDataItem.pushDataId}`);
|
|
80
72
|
const config = resolveXYConfig(cfg);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
73
|
+
if ("pushDataId" in triggerData) {
|
|
74
|
+
// 单条查询:按 pushDataId 返回
|
|
75
|
+
log(`[BOT] - Type: pushDataId = ${triggerData.pushDataId}`);
|
|
76
|
+
const pushDataItem = await getPushDataById(triggerData.pushDataId);
|
|
77
|
+
if (!pushDataItem) {
|
|
78
|
+
error(`[BOT] ❌ pushData not found for ID: ${triggerData.pushDataId}`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
log(`[BOT] ✅ Found pushData, sending direct response`);
|
|
82
|
+
await sendA2AResponse({
|
|
83
|
+
config,
|
|
84
|
+
sessionId: parsed.sessionId,
|
|
85
|
+
taskId: parsed.taskId,
|
|
86
|
+
messageId: parsed.messageId,
|
|
87
|
+
text: pushDataItem.dataDetail,
|
|
88
|
+
append: false,
|
|
89
|
+
final: true,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// 批量查询:按 timestamp 返回所有之后的 push 数据
|
|
94
|
+
log(`[BOT] - Type: timestamp = ${triggerData.timestamp}`);
|
|
95
|
+
const items = await getPushDataAfterTimestamp(triggerData.timestamp);
|
|
96
|
+
const result = items.map(item => ({
|
|
97
|
+
pushDataId: item.pushDataId,
|
|
98
|
+
pushData: item.dataDetail,
|
|
99
|
+
}));
|
|
100
|
+
log(`[BOT] ✅ Found ${result.length} pushData items after timestamp, sending response`);
|
|
101
|
+
await sendA2AResponse({
|
|
102
|
+
config,
|
|
103
|
+
sessionId: parsed.sessionId,
|
|
104
|
+
taskId: parsed.taskId,
|
|
105
|
+
messageId: parsed.messageId,
|
|
106
|
+
text: JSON.stringify(result),
|
|
107
|
+
append: false,
|
|
108
|
+
final: true,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
91
111
|
log(`[BOT] ✅ Trigger response sent successfully, exiting early`);
|
|
92
112
|
return; // 提前返回,不继续处理
|
|
93
113
|
}
|
package/dist/src/channel.js
CHANGED
|
@@ -29,7 +29,6 @@ import { xiaoyiAddCollectionTool } from "./tools/xiaoyi-add-collection-tool.js";
|
|
|
29
29
|
import { xiaoyiDeleteCollectionTool } from "./tools/xiaoyi-delete-collection-tool.js";
|
|
30
30
|
import { saveMediaToGalleryTool } from "./tools/save-media-to-gallery-tool.js";
|
|
31
31
|
import { saveFileToPhoneTool } from "./tools/save-file-to-phone-tool.js";
|
|
32
|
-
import { findPcDevicesTool } from "./tools/find-pc-devices-tool.js";
|
|
33
32
|
import { filterToolsByDevice } from "./tools/device-tool-map.js";
|
|
34
33
|
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
35
34
|
import { logger } from "./utils/logger.js";
|
|
@@ -72,7 +71,7 @@ export const xyPlugin = {
|
|
|
72
71
|
},
|
|
73
72
|
outbound: xyOutbound,
|
|
74
73
|
agentTools: () => {
|
|
75
|
-
const allTools = [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchContactTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool, callPhoneTool, searchMessageTool, sendMessageTool, searchFileTool, uploadFileTool, createAlarmTool, searchAlarmTool, modifyAlarmTool, deleteAlarmTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, xiaoyiCollectionTool, xiaoyiAddCollectionTool, xiaoyiDeleteCollectionTool, saveMediaToGalleryTool, saveFileToPhoneTool
|
|
74
|
+
const allTools = [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchContactTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool, callPhoneTool, searchMessageTool, sendMessageTool, searchFileTool, uploadFileTool, createAlarmTool, searchAlarmTool, modifyAlarmTool, deleteAlarmTool, sendFileToUserTool, viewPushResultTool, imageReadingTool, timestampToUtc8Tool, xiaoyiCollectionTool, xiaoyiAddCollectionTool, xiaoyiDeleteCollectionTool, saveMediaToGalleryTool, saveFileToPhoneTool];
|
|
76
75
|
const ctx = getCurrentSessionContext();
|
|
77
76
|
const filtered = filterToolsByDevice(allTools, ctx?.deviceType);
|
|
78
77
|
logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
|
package/dist/src/parser.d.ts
CHANGED
|
@@ -49,13 +49,16 @@ export declare function extractPushId(parts: A2AMessagePart[]): string | null;
|
|
|
49
49
|
* (same level as push_id).
|
|
50
50
|
*/
|
|
51
51
|
export declare function extractDeviceType(parts: A2AMessagePart[]): string | null;
|
|
52
|
+
export type TriggerData = {
|
|
53
|
+
pushDataId: string;
|
|
54
|
+
} | {
|
|
55
|
+
timestamp: string;
|
|
56
|
+
};
|
|
52
57
|
/**
|
|
53
58
|
* Extract Trigger event data from message parts.
|
|
54
|
-
* Looks for Trigger events with pushDataId in data parts.
|
|
59
|
+
* Looks for Trigger events with pushDataId or timestamp in data parts.
|
|
55
60
|
*/
|
|
56
|
-
export declare function extractTriggerData(parts: A2AMessagePart[]):
|
|
57
|
-
pushDataId: string;
|
|
58
|
-
} | null;
|
|
61
|
+
export declare function extractTriggerData(parts: A2AMessagePart[]): TriggerData | null;
|
|
59
62
|
/**
|
|
60
63
|
* Validate A2A request structure.
|
|
61
64
|
*/
|
package/dist/src/parser.js
CHANGED
|
@@ -90,7 +90,7 @@ export function extractDeviceType(parts) {
|
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
92
|
* Extract Trigger event data from message parts.
|
|
93
|
-
* Looks for Trigger events with pushDataId in data parts.
|
|
93
|
+
* Looks for Trigger events with pushDataId or timestamp in data parts.
|
|
94
94
|
*/
|
|
95
95
|
export function extractTriggerData(parts) {
|
|
96
96
|
for (const part of parts) {
|
|
@@ -99,10 +99,15 @@ export function extractTriggerData(parts) {
|
|
|
99
99
|
if (Array.isArray(events)) {
|
|
100
100
|
for (const event of events) {
|
|
101
101
|
if (event.header?.namespace === "Common" && event.header?.name === "Trigger") {
|
|
102
|
-
const
|
|
102
|
+
const dataMap = event.payload?.dataMap;
|
|
103
|
+
const pushDataId = dataMap?.pushDataId;
|
|
103
104
|
if (pushDataId && typeof pushDataId === "string") {
|
|
104
105
|
return { pushDataId };
|
|
105
106
|
}
|
|
107
|
+
const timestamp = dataMap?.timestamp;
|
|
108
|
+
if (timestamp && typeof timestamp === "string") {
|
|
109
|
+
return { timestamp };
|
|
110
|
+
}
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
113
|
}
|
package/dist/src/provider.js
CHANGED
|
@@ -101,6 +101,12 @@ export const xiaoyiProvider = {
|
|
|
101
101
|
if (context.systemPrompt) {
|
|
102
102
|
console.log(`[xiaoyiprovider] system prompt length: ${context.systemPrompt.length}`);
|
|
103
103
|
}
|
|
104
|
+
// 在发送给模型前,删除 systemPrompt 中 ## Tooling 与 TOOLS.md 声明之间的内容
|
|
105
|
+
if (context.systemPrompt) {
|
|
106
|
+
const before = context.systemPrompt.length;
|
|
107
|
+
context.systemPrompt = context.systemPrompt.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");
|
|
108
|
+
console.log(`[xiaoyiprovider] system prompt trimmed: ${before} -> ${context.systemPrompt.length}`);
|
|
109
|
+
}
|
|
104
110
|
const stream = await underlying(model, context, {
|
|
105
111
|
...options,
|
|
106
112
|
headers: {
|
|
@@ -109,8 +115,28 @@ export const xiaoyiProvider = {
|
|
|
109
115
|
},
|
|
110
116
|
});
|
|
111
117
|
// 异步监听输出(不阻塞 stream 返回)
|
|
112
|
-
stream.result().then((
|
|
113
|
-
|
|
118
|
+
stream.result().then((result) => {
|
|
119
|
+
console.log(`[xiaoyiprovider] stream completed, usage: input=${result.usage?.input} output=${result.usage?.output}`);
|
|
120
|
+
}, (err) => console.log(`[xiaoyiprovider] stream error: ${err}`));
|
|
121
|
+
// 用 Proxy 拦截 result(),检查 usage 是否为全零(表示上下文超长)
|
|
122
|
+
return new Proxy(stream, {
|
|
123
|
+
get(target, prop, receiver) {
|
|
124
|
+
if (prop === "result") {
|
|
125
|
+
const originalResult = target.result.bind(target);
|
|
126
|
+
return () => originalResult().then((result) => {
|
|
127
|
+
if (result?.usage?.input === 0 && result?.usage?.output === 0) {
|
|
128
|
+
const error = new Error("This model's maximum context length was exceeded.");
|
|
129
|
+
error.type = "invalid_request_error";
|
|
130
|
+
error.code = "context_length_exceeded";
|
|
131
|
+
error.param = "messages";
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return Reflect.get(target, prop, receiver);
|
|
138
|
+
},
|
|
139
|
+
});
|
|
114
140
|
};
|
|
115
141
|
},
|
|
116
142
|
};
|
|
@@ -8,13 +8,15 @@ import { getCurrentSessionContext } from "./session-manager.js";
|
|
|
8
8
|
export const searchFileTool = {
|
|
9
9
|
name: "search_file",
|
|
10
10
|
label: "Search File",
|
|
11
|
-
description:
|
|
11
|
+
description: `搜索手机文件系统的文件。
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
【重要】使用约束:此工具仅在用户显著说明要从手机搜索时才执行,例如:
|
|
14
|
+
- "从我手机里面搜索xxxx"
|
|
15
|
+
- "从手机文件系统找一下xxxx"
|
|
16
|
+
- "在手机上查找文件xxxx"
|
|
17
|
+
- "搜索手机里的文件"
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
如果用户没有明确说明从手机搜索(如仅说"搜索文件"、"找一下xxxx"),应默认从 openclaw 本地的文件系统查询,不要调用此工具。
|
|
18
20
|
|
|
19
21
|
功能说明:根据关键词搜索文件名称或内容,返回匹配的文件列表(包括文件名、路径、大小、修改时间等信息)。
|
|
20
22
|
|
|
@@ -26,10 +28,6 @@ export const searchFileTool = {
|
|
|
26
28
|
type: "string",
|
|
27
29
|
description: "搜索关键词,用于匹配文件名称、后缀名或文件内容",
|
|
28
30
|
},
|
|
29
|
-
udid: {
|
|
30
|
-
type: "string",
|
|
31
|
-
description: "PC/电脑设备ID。当搜索PC/电脑上的文件时,需要先通过 find_pc_devices 工具获取设备ID后传入。搜索手机文件时不需要传入此参数。",
|
|
32
|
-
},
|
|
33
31
|
},
|
|
34
32
|
required: ["query"],
|
|
35
33
|
},
|
|
@@ -64,7 +62,6 @@ export const searchFileTool = {
|
|
|
64
62
|
timeOut: 5,
|
|
65
63
|
intentParam: {
|
|
66
64
|
query: params.query.trim(),
|
|
67
|
-
...(params.udid ? { udid: params.udid } : {}),
|
|
68
65
|
},
|
|
69
66
|
permissionId: [],
|
|
70
67
|
achieveType: "INTENT",
|
|
@@ -16,19 +16,14 @@ import { getCurrentSessionContext } from "./session-manager.js";
|
|
|
16
16
|
export const uploadFileTool = {
|
|
17
17
|
name: "upload_file",
|
|
18
18
|
label: "Upload File",
|
|
19
|
-
description:
|
|
19
|
+
description: `工具能力描述:将手机本地文件上传并获取可公网访问的 URL。
|
|
20
20
|
|
|
21
21
|
前置工具调用:此工具使用前必须先调用 search_file 或者 QueryCollection 工具获取文件的 uri
|
|
22
22
|
|
|
23
|
-
使用场景与调用流程:
|
|
24
|
-
1. 上传手机文件:直接调用此工具,无需传入 udid。
|
|
25
|
-
2. 上传PC/电脑文件:当用户要求上传PC/电脑上的文件时,需要先通过 find_pc_devices 工具获取设备ID(udid),然后将 udid 传入此工具。
|
|
26
|
-
|
|
27
23
|
工具参数说明:
|
|
28
24
|
a. 入参中的fileInfos数组,每个元素必须包含mediaUri字段(对应于search_file工具或者QueryCollection返回结果中的uri),必须与search_file或者QueryCollection结果中对应的uri完全保持一致,不要自行修改。
|
|
29
25
|
b. fileInfos中的timeout字段是可选的,表示上传文件超时时间,单位是毫秒,默认是20000(20秒)。
|
|
30
|
-
c. fileInfos
|
|
31
|
-
d. udid 是PC/电脑设备ID,仅在上传PC/电脑文件时需要传入(通过 find_pc_devices 工具获取)。
|
|
26
|
+
c. fileInfos 是文件在手机本地的信息数组(从 search_file 工具响应中获取)。限制:每次最多支持传入 5 条文件信息。
|
|
32
27
|
|
|
33
28
|
注意事项:
|
|
34
29
|
a. 操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。
|
|
@@ -41,10 +36,6 @@ export const uploadFileTool = {
|
|
|
41
36
|
// 具体的类型验证和转换在 execute 函数内部进行
|
|
42
37
|
description: "文件信息数组,每个元素包含mediaUri(必需)和timeout(可选,默认20000)。必须先通过 search_file 工具获取。每次最多支持 5 条文件信息。",
|
|
43
38
|
},
|
|
44
|
-
udid: {
|
|
45
|
-
type: "string",
|
|
46
|
-
description: "PC/电脑设备ID。当上传PC/电脑上的文件时,需要先通过 find_pc_devices 工具获取设备ID后传入。上传手机文件时不需要传入此参数。",
|
|
47
|
-
},
|
|
48
39
|
},
|
|
49
40
|
required: ["fileInfos"],
|
|
50
41
|
},
|
|
@@ -108,7 +99,7 @@ export const uploadFileTool = {
|
|
|
108
99
|
// Get WebSocket manager
|
|
109
100
|
const wsManager = getXYWebSocketManager(config);
|
|
110
101
|
// Get public URLs for the files
|
|
111
|
-
const fileUrls = await getFileUrls(wsManager, config, sessionId, taskId, messageId, fileInfos
|
|
102
|
+
const fileUrls = await getFileUrls(wsManager, config, sessionId, taskId, messageId, fileInfos);
|
|
112
103
|
return {
|
|
113
104
|
content: [
|
|
114
105
|
{
|
|
@@ -127,7 +118,7 @@ export const uploadFileTool = {
|
|
|
127
118
|
* Get public URLs for files using fileInfos
|
|
128
119
|
* Returns array of publicly accessible file URLs
|
|
129
120
|
*/
|
|
130
|
-
async function getFileUrls(wsManager, config, sessionId, taskId, messageId, fileInfos
|
|
121
|
+
async function getFileUrls(wsManager, config, sessionId, taskId, messageId, fileInfos) {
|
|
131
122
|
const command = {
|
|
132
123
|
header: {
|
|
133
124
|
namespace: "Common",
|
|
@@ -145,7 +136,6 @@ async function getFileUrls(wsManager, config, sessionId, taskId, messageId, file
|
|
|
145
136
|
timeOut: 5,
|
|
146
137
|
intentParam: {
|
|
147
138
|
fileInfos: fileInfos,
|
|
148
|
-
...(udid ? { udid } : {}),
|
|
149
139
|
},
|
|
150
140
|
permissionId: [],
|
|
151
141
|
achieveType: "INTENT",
|
|
@@ -22,6 +22,11 @@ export declare function searchPushData(keywords?: string): Promise<PushDataItem[
|
|
|
22
22
|
* 获取所有推送数据
|
|
23
23
|
*/
|
|
24
24
|
export declare function getAllPushData(): Promise<PushDataItem[]>;
|
|
25
|
+
/**
|
|
26
|
+
* 获取指定时间戳之后的所有推送数据
|
|
27
|
+
* @param timestamp - Unix 时间戳(秒或毫秒)
|
|
28
|
+
*/
|
|
29
|
+
export declare function getPushDataAfterTimestamp(timestamp: string): Promise<PushDataItem[]>;
|
|
25
30
|
/**
|
|
26
31
|
* 清空所有推送数据(用于测试或重置)
|
|
27
32
|
*/
|
|
@@ -157,6 +157,39 @@ export async function getAllPushData() {
|
|
|
157
157
|
return [];
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* 将 Unix 时间戳(秒或毫秒)转为 YYYYMMDD HHmmss 北京时间字符串
|
|
162
|
+
*/
|
|
163
|
+
function unixToBeijingStr(ts) {
|
|
164
|
+
let ms = Number(ts);
|
|
165
|
+
if (isNaN(ms))
|
|
166
|
+
return "";
|
|
167
|
+
// 秒级时间戳(小于 1e12)转为毫秒
|
|
168
|
+
if (ms < 1e12)
|
|
169
|
+
ms *= 1000;
|
|
170
|
+
return formatBeijingTime(new Date(ms));
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 获取指定时间戳之后的所有推送数据
|
|
174
|
+
* @param timestamp - Unix 时间戳(秒或毫秒)
|
|
175
|
+
*/
|
|
176
|
+
export async function getPushDataAfterTimestamp(timestamp) {
|
|
177
|
+
try {
|
|
178
|
+
const target = unixToBeijingStr(timestamp);
|
|
179
|
+
if (!target) {
|
|
180
|
+
logger.warn(`[PushDataManager] Invalid timestamp: ${timestamp}`);
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
const list = await readPushDataList();
|
|
184
|
+
const results = list.filter(item => item.time >= target);
|
|
185
|
+
logger.log(`[PushDataManager] Query after timestamp ${timestamp} (UTC+8: ${target}): found ${results.length} items`);
|
|
186
|
+
return results;
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
logger.error(`[PushDataManager] Failed to get pushData after timestamp:`, error);
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
160
193
|
/**
|
|
161
194
|
* 清空所有推送数据(用于测试或重置)
|
|
162
195
|
*/
|