@ynhcj/xiaoyi-channel 0.0.38-beta → 0.0.40-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.
- package/dist/src/bot.js +17 -13
- package/dist/src/channel.js +5 -5
- package/dist/src/client.js +11 -24
- package/dist/src/config.js +2 -2
- package/dist/src/monitor.js +12 -0
- package/dist/src/outbound.js +122 -88
- package/dist/src/push.d.ts +7 -1
- package/dist/src/push.js +22 -7
- package/dist/src/tools/search-photo-gallery-tool.js +30 -2
- package/dist/src/tools/view-push-result-tool.d.ts +5 -0
- package/dist/src/tools/view-push-result-tool.js +118 -0
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +5 -0
- package/dist/src/tools/xiaoyi-collection-tool.js +190 -0
- package/dist/src/trigger-handler.d.ts +19 -0
- package/dist/src/trigger-handler.js +91 -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 +218 -270
- package/package.json +1 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { searchPushData, getAllPushData } from "../utils/pushdata-manager.js";
|
|
2
|
+
import { logger } from "../utils/logger.js";
|
|
3
|
+
/**
|
|
4
|
+
* 查看推送任务执行结果工具
|
|
5
|
+
* 支持关键词搜索或查看最近的推送记录
|
|
6
|
+
*/
|
|
7
|
+
export const viewPushResultTool = {
|
|
8
|
+
name: "view_push_result",
|
|
9
|
+
label: "View Push Task Result",
|
|
10
|
+
description: `查看定时任务或推送消息的执行结果。当用户说"查看我xxx的定时任务执行结果"、"查看我的xxxx的推送消息"或类似语料时调用此工具。
|
|
11
|
+
|
|
12
|
+
功能说明:
|
|
13
|
+
- 支持关键词搜索:如果用户提到具体任务名称或内容,可以按关键词筛选
|
|
14
|
+
- 无关键词时:返回最近的推送记录(默认10条)
|
|
15
|
+
- 返回内容包括:推送ID、时间、内容摘要
|
|
16
|
+
|
|
17
|
+
使用场景:
|
|
18
|
+
- "查看我昨天的定时任务执行结果"
|
|
19
|
+
- "帮我看看天气推送消息"
|
|
20
|
+
- "查看最近的推送记录"
|
|
21
|
+
- "我的提醒任务执行了吗"`,
|
|
22
|
+
parameters: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
keywords: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "可选的搜索关键词,用于筛选推送记录。如果用户提到具体任务名称或内容,将其作为关键词传入。例如:'天气'、'提醒'、'会议'等。",
|
|
28
|
+
},
|
|
29
|
+
limit: {
|
|
30
|
+
type: "number",
|
|
31
|
+
description: "返回的最大记录数,默认10条,最多50条",
|
|
32
|
+
default: 10,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: [],
|
|
36
|
+
},
|
|
37
|
+
async execute(toolCallId, params) {
|
|
38
|
+
logger.log(`[VIEW_PUSH_RESULT_TOOL] 🚀 Starting execution`);
|
|
39
|
+
logger.log(`[VIEW_PUSH_RESULT_TOOL] - toolCallId: ${toolCallId}`);
|
|
40
|
+
logger.log(`[VIEW_PUSH_RESULT_TOOL] - params:`, JSON.stringify(params));
|
|
41
|
+
const keywords = params.keywords?.trim();
|
|
42
|
+
const limit = Math.min(params.limit || 10, 50); // 限制最多50条
|
|
43
|
+
try {
|
|
44
|
+
logger.log(`[VIEW_PUSH_RESULT_TOOL] 🔍 Searching push data...`);
|
|
45
|
+
logger.log(`[VIEW_PUSH_RESULT_TOOL] - keywords: ${keywords || '(none)'}`);
|
|
46
|
+
logger.log(`[VIEW_PUSH_RESULT_TOOL] - limit: ${limit}`);
|
|
47
|
+
// 根据是否有关键词决定调用哪个方法
|
|
48
|
+
let results = keywords
|
|
49
|
+
? await searchPushData(keywords)
|
|
50
|
+
: await getAllPushData();
|
|
51
|
+
logger.log(`[VIEW_PUSH_RESULT_TOOL] Found ${results.length} items before limit`);
|
|
52
|
+
// 按时间倒序排序(最新的在前)
|
|
53
|
+
results.sort((a, b) => b.time.localeCompare(a.time));
|
|
54
|
+
// 限制返回条数
|
|
55
|
+
results = results.slice(0, limit);
|
|
56
|
+
if (results.length === 0) {
|
|
57
|
+
logger.log(`[VIEW_PUSH_RESULT_TOOL] ⚠️ No results found`);
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: JSON.stringify({
|
|
63
|
+
success: true,
|
|
64
|
+
count: 0,
|
|
65
|
+
items: [],
|
|
66
|
+
message: keywords
|
|
67
|
+
? `未找到包含关键词"${keywords}"的推送记录`
|
|
68
|
+
: "暂无推送记录",
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// 格式化返回结果
|
|
75
|
+
const formattedItems = results.map((item) => ({
|
|
76
|
+
pushDataId: item.pushDataId.substring(0, 8), // 只显示前8位
|
|
77
|
+
fullPushDataId: item.pushDataId, // 完整ID用于追溯
|
|
78
|
+
time: item.time,
|
|
79
|
+
dataDetail: item.dataDetail.length > 200
|
|
80
|
+
? item.dataDetail.substring(0, 200) + "..."
|
|
81
|
+
: item.dataDetail,
|
|
82
|
+
fullLength: item.dataDetail.length,
|
|
83
|
+
}));
|
|
84
|
+
logger.log(`[VIEW_PUSH_RESULT_TOOL] ✅ Returning ${formattedItems.length} items`);
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: JSON.stringify({
|
|
90
|
+
success: true,
|
|
91
|
+
count: formattedItems.length,
|
|
92
|
+
totalMatched: results.length,
|
|
93
|
+
items: formattedItems,
|
|
94
|
+
message: keywords
|
|
95
|
+
? `找到 ${formattedItems.length} 条包含"${keywords}"的推送记录`
|
|
96
|
+
: `返回最近 ${formattedItems.length} 条推送记录`,
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
logger.error(`[VIEW_PUSH_RESULT_TOOL] ❌ Failed to execute:`, error);
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: "text",
|
|
108
|
+
text: JSON.stringify({
|
|
109
|
+
success: false,
|
|
110
|
+
error: error instanceof Error ? error.message : String(error),
|
|
111
|
+
message: "查询推送记录失败",
|
|
112
|
+
}),
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* XY collection tool - retrieves user's collection data from XiaoYi.
|
|
7
|
+
* Returns personalized knowledge data saved in user's collection.
|
|
8
|
+
*/
|
|
9
|
+
export const xiaoyiCollectionTool = {
|
|
10
|
+
name: "xiaoyi_collection",
|
|
11
|
+
label: "XiaoYi Collection",
|
|
12
|
+
description: "检索用户在小艺收藏中记下来的公共知识数据,可以给用户提供个性化体验。当用户语料中涉及从我的小艺收藏或者查看我的收藏或者我xx时候收藏的xxx帮我看一下这种类型的语料的时候需要使用此工具。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
|
|
13
|
+
parameters: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
queryAll: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "描述是否需要查询用户所有收藏数据,默认为true",
|
|
19
|
+
default: "true",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
required: [],
|
|
23
|
+
},
|
|
24
|
+
async execute(toolCallId, params) {
|
|
25
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] 🚀 Starting execution`);
|
|
26
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] - toolCallId: ${toolCallId}`);
|
|
27
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] - params:`, JSON.stringify(params));
|
|
28
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
29
|
+
// Get session context
|
|
30
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] 🔍 Attempting to get session context...`);
|
|
31
|
+
const sessionContext = getCurrentSessionContext();
|
|
32
|
+
if (!sessionContext) {
|
|
33
|
+
logger.error(`[XIAOYI_COLLECTION_TOOL] ❌ FAILED: No active session found!`);
|
|
34
|
+
logger.error(`[XIAOYI_COLLECTION_TOOL] - toolCallId: ${toolCallId}`);
|
|
35
|
+
throw new Error("No active XY session found. XiaoYi collection tool can only be used during an active conversation.");
|
|
36
|
+
}
|
|
37
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] ✅ Session context found`);
|
|
38
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
39
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
40
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
41
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
42
|
+
// Get WebSocket manager
|
|
43
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] 🔌 Getting WebSocket manager...`);
|
|
44
|
+
const wsManager = getXYWebSocketManager(config);
|
|
45
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] ✅ WebSocket manager obtained`);
|
|
46
|
+
// Build QueryCollection command
|
|
47
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] 📦 Building QueryCollection command...`);
|
|
48
|
+
const command = {
|
|
49
|
+
header: {
|
|
50
|
+
namespace: "Common",
|
|
51
|
+
name: "Action",
|
|
52
|
+
},
|
|
53
|
+
payload: {
|
|
54
|
+
cardParam: {},
|
|
55
|
+
executeParam: {
|
|
56
|
+
executeMode: "background",
|
|
57
|
+
intentName: "QueryCollection",
|
|
58
|
+
bundleName: "com.huawei.hmos.vassistant",
|
|
59
|
+
needUnlock: true,
|
|
60
|
+
actionResponse: true,
|
|
61
|
+
appType: "OHOS_APP",
|
|
62
|
+
timeOut: 5,
|
|
63
|
+
intentParam: {
|
|
64
|
+
queryAll: params.queryAll || "true",
|
|
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
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] ⏳ Setting up promise to wait for collection query response...`);
|
|
83
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] - Timeout: 60 seconds`);
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const timeout = setTimeout(() => {
|
|
86
|
+
logger.error(`[XIAOYI_COLLECTION_TOOL] ⏰ Timeout: No response received within 60 seconds`);
|
|
87
|
+
wsManager.off("data-event", handler);
|
|
88
|
+
reject(new Error("查询小艺收藏超时(60秒)"));
|
|
89
|
+
}, 60000);
|
|
90
|
+
// Listen for data events from WebSocket
|
|
91
|
+
const handler = (event) => {
|
|
92
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
93
|
+
if (event.intentName === "QueryCollection") {
|
|
94
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] 🎯 QueryCollection event received`);
|
|
95
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] - status: ${event.status}`);
|
|
96
|
+
clearTimeout(timeout);
|
|
97
|
+
wsManager.off("data-event", handler);
|
|
98
|
+
if (event.status === "success" && event.outputs) {
|
|
99
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] ✅ Collection query completed successfully`);
|
|
100
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] - outputs:`, JSON.stringify(event.outputs));
|
|
101
|
+
// Check for error code first
|
|
102
|
+
if (event.outputs.code && event.outputs.code !== 0) {
|
|
103
|
+
logger.error(`[XIAOYI_COLLECTION_TOOL] ❌ Query failed with error code: ${event.outputs.code}`);
|
|
104
|
+
reject(new Error(`查询小艺收藏失败 (错误码: ${event.outputs.code})`));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// Get the result from outputs
|
|
108
|
+
const result = event.outputs.result;
|
|
109
|
+
// Check if result exists
|
|
110
|
+
if (!result) {
|
|
111
|
+
logger.warn(`[XIAOYI_COLLECTION_TOOL] ⚠️ No collection data found`);
|
|
112
|
+
resolve({
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: JSON.stringify({
|
|
117
|
+
success: true,
|
|
118
|
+
memoryInfo: [],
|
|
119
|
+
message: "未找到收藏数据"
|
|
120
|
+
}),
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Extract memoryInfo from the nested result structure
|
|
127
|
+
const memoryInfo = result.result?.memoryInfo || [];
|
|
128
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] 📊 Collections found: ${memoryInfo.length} items`);
|
|
129
|
+
// Process and simplify the collection data
|
|
130
|
+
const simplifiedCollections = memoryInfo.map((item) => ({
|
|
131
|
+
uuid: item.uuid,
|
|
132
|
+
type: item.type,
|
|
133
|
+
status: item.status,
|
|
134
|
+
collectionTime: item.collectionTime,
|
|
135
|
+
editTime: item.editTime,
|
|
136
|
+
title: item.linkTitle || item.aiTitle || item.textTitle || item.imageTitle || item.podcastTitle || "",
|
|
137
|
+
description: item.description || item.abstract || "",
|
|
138
|
+
content: item.textContent || "",
|
|
139
|
+
linkUrl: item.linkUrl,
|
|
140
|
+
linkType: item.linkType,
|
|
141
|
+
appName: item.appNameFromPab || item.appName || "",
|
|
142
|
+
labels: item.label || [],
|
|
143
|
+
collectionMethod: item.collectionMethod,
|
|
144
|
+
}));
|
|
145
|
+
// Return the result with valid string content
|
|
146
|
+
resolve({
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: JSON.stringify({
|
|
151
|
+
success: true,
|
|
152
|
+
totalResults: simplifiedCollections.length,
|
|
153
|
+
collections: simplifiedCollections,
|
|
154
|
+
message: result.message,
|
|
155
|
+
}),
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
logger.error(`[XIAOYI_COLLECTION_TOOL] ❌ Collection query failed`);
|
|
162
|
+
logger.error(`[XIAOYI_COLLECTION_TOOL] - status: ${event.status}`);
|
|
163
|
+
reject(new Error(`查询小艺收藏失败: ${event.status}`));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
// Register event handler
|
|
168
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] 📡 Registering data-event handler on WebSocket manager`);
|
|
169
|
+
wsManager.on("data-event", handler);
|
|
170
|
+
// Send the command
|
|
171
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] 📤 Sending QueryCollection command...`);
|
|
172
|
+
sendCommand({
|
|
173
|
+
config,
|
|
174
|
+
sessionId,
|
|
175
|
+
taskId,
|
|
176
|
+
messageId,
|
|
177
|
+
command,
|
|
178
|
+
})
|
|
179
|
+
.then(() => {
|
|
180
|
+
logger.log(`[XIAOYI_COLLECTION_TOOL] ✅ Command sent successfully, waiting for response...`);
|
|
181
|
+
})
|
|
182
|
+
.catch((error) => {
|
|
183
|
+
logger.error(`[XIAOYI_COLLECTION_TOOL] ❌ Failed to send command:`, error);
|
|
184
|
+
clearTimeout(timeout);
|
|
185
|
+
wsManager.off("data-event", handler);
|
|
186
|
+
reject(error);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Trigger 事件上下文
|
|
4
|
+
*/
|
|
5
|
+
export interface TriggerEventContext {
|
|
6
|
+
event: any;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
taskId: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 处理 Trigger 事件
|
|
12
|
+
* 当用户在手机侧点击推送消息时触发
|
|
13
|
+
*
|
|
14
|
+
* @param context - Trigger 事件上下文(包含 event, sessionId, taskId)
|
|
15
|
+
* @param cfg - OpenClaw 配置
|
|
16
|
+
* @param runtime - 运行时环境
|
|
17
|
+
* @param accountId - 账号ID
|
|
18
|
+
*/
|
|
19
|
+
export declare function handleTriggerEvent(context: TriggerEventContext, cfg: ClawdbotConfig, runtime: RuntimeEnv, accountId: string): Promise<void>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Trigger 事件处理器
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { getPushDataById } from "./utils/pushdata-manager.js";
|
|
4
|
+
import { handleXYMessage } from "./bot.js";
|
|
5
|
+
/**
|
|
6
|
+
* 处理 Trigger 事件
|
|
7
|
+
* 当用户在手机侧点击推送消息时触发
|
|
8
|
+
*
|
|
9
|
+
* @param context - Trigger 事件上下文(包含 event, sessionId, taskId)
|
|
10
|
+
* @param cfg - OpenClaw 配置
|
|
11
|
+
* @param runtime - 运行时环境
|
|
12
|
+
* @param accountId - 账号ID
|
|
13
|
+
*/
|
|
14
|
+
export async function handleTriggerEvent(context, cfg, runtime, accountId) {
|
|
15
|
+
const log = runtime?.log ?? console.log;
|
|
16
|
+
const error = runtime?.error ?? console.error;
|
|
17
|
+
try {
|
|
18
|
+
const { event, sessionId, taskId } = context;
|
|
19
|
+
log(`[TRIGGER_HANDLER] 📌 Received Trigger event`);
|
|
20
|
+
log(`[TRIGGER_HANDLER] Event:`, JSON.stringify(event, null, 2));
|
|
21
|
+
log(`[TRIGGER_HANDLER] Context - sessionId: ${sessionId}, taskId: ${taskId}`);
|
|
22
|
+
if (!sessionId) {
|
|
23
|
+
error(`[TRIGGER_HANDLER] ❌ Missing sessionId in context`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!taskId) {
|
|
27
|
+
error(`[TRIGGER_HANDLER] ❌ Missing taskId in context`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// 从 event.payload.dataMap 中提取 pushDataId
|
|
31
|
+
const pushDataId = event.payload?.dataMap?.pushDataId;
|
|
32
|
+
if (!pushDataId) {
|
|
33
|
+
error(`[TRIGGER_HANDLER] ❌ Missing pushDataId in Trigger event payload`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
log(`[TRIGGER_HANDLER] 🔍 Looking up pushDataId: ${pushDataId}`);
|
|
37
|
+
// 根据 pushDataId 查询原始数据
|
|
38
|
+
const pushDataItem = await getPushDataById(pushDataId);
|
|
39
|
+
if (!pushDataItem) {
|
|
40
|
+
error(`[TRIGGER_HANDLER] ❌ pushData not found for ID: ${pushDataId}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
log(`[TRIGGER_HANDLER] ✅ Found pushData`);
|
|
44
|
+
log(`[TRIGGER_HANDLER] - pushDataId: ${pushDataItem.pushDataId}`);
|
|
45
|
+
log(`[TRIGGER_HANDLER] - time: ${pushDataItem.time}`);
|
|
46
|
+
log(`[TRIGGER_HANDLER] - dataDetail length: ${pushDataItem.dataDetail.length} chars`);
|
|
47
|
+
// 拼接消息
|
|
48
|
+
const prefixMessage = "这是定时任务的执行结果,你检查一下是否依赖手机侧的工具执行,如果依赖则在本轮任务中完成手机侧任务执行,如果不依赖,则直接把完整的执行结果返回给用户";
|
|
49
|
+
const fullMessage = `${prefixMessage}\n\n${pushDataItem.dataDetail}`;
|
|
50
|
+
log(`[TRIGGER_HANDLER] 📝 Constructed message (length: ${fullMessage.length} chars)`);
|
|
51
|
+
// 构造 A2A 消息
|
|
52
|
+
// 使用从 WebSocket 传递过来的 sessionId 和 taskId(新的 taskId)
|
|
53
|
+
// 这样可以触发 steer 模式,覆盖之前正在执行的任务
|
|
54
|
+
const messageId = randomUUID();
|
|
55
|
+
const a2aMessage = {
|
|
56
|
+
jsonrpc: "2.0",
|
|
57
|
+
method: "sendMessage",
|
|
58
|
+
id: messageId,
|
|
59
|
+
params: {
|
|
60
|
+
id: taskId, // 使用新的 taskId(点击推送时生成)
|
|
61
|
+
sessionId: sessionId, // 使用原始会话 sessionId
|
|
62
|
+
agentLoginSessionId: "",
|
|
63
|
+
message: {
|
|
64
|
+
role: "user",
|
|
65
|
+
parts: [
|
|
66
|
+
{
|
|
67
|
+
kind: "text",
|
|
68
|
+
text: fullMessage,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
log(`[TRIGGER_HANDLER] 🚀 Dispatching A2A message to bot handler`);
|
|
75
|
+
log(`[TRIGGER_HANDLER] - sessionId: ${sessionId}`);
|
|
76
|
+
log(`[TRIGGER_HANDLER] - taskId: ${taskId} (NEW - will trigger steer mode if configured)`);
|
|
77
|
+
log(`[TRIGGER_HANDLER] - messageId: ${messageId}`);
|
|
78
|
+
// 透传给 openclaw 处理
|
|
79
|
+
// 如果配置了 steer 模式,且该 sessionId 有活跃任务,这个新的 taskId 会覆盖旧的
|
|
80
|
+
await handleXYMessage({
|
|
81
|
+
cfg,
|
|
82
|
+
runtime,
|
|
83
|
+
message: a2aMessage,
|
|
84
|
+
accountId,
|
|
85
|
+
});
|
|
86
|
+
log(`[TRIGGER_HANDLER] ✅ Trigger event handled successfully`);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
error(`[TRIGGER_HANDLER] ❌ Failed to handle Trigger event:`, err);
|
|
90
|
+
}
|
|
91
|
+
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export interface XYChannelConfig {
|
|
2
2
|
enabled: boolean;
|
|
3
|
-
|
|
4
|
-
wsUrl2: string;
|
|
3
|
+
wsUrl: string;
|
|
5
4
|
apiKey: string;
|
|
6
5
|
uid: string;
|
|
7
6
|
agentId: string;
|
|
@@ -159,9 +158,3 @@ export interface FileUploadCompleteRequest {
|
|
|
159
158
|
export interface FileUploadCompleteResponse {
|
|
160
159
|
fileId: string;
|
|
161
160
|
}
|
|
162
|
-
export type ServerIdentifier = "server1" | "server2";
|
|
163
|
-
export interface SessionBinding {
|
|
164
|
-
sessionId: string;
|
|
165
|
-
server: ServerIdentifier;
|
|
166
|
-
boundAt: number;
|
|
167
|
-
}
|
package/dist/src/types.js
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
// Type definitions for XY Channel - A2A protocol and configuration
|
|
2
2
|
export {};
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Session Management
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// ✅ Removed ServerIdentifier and SessionBinding - no longer needed with single connection
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 推送数据项
|
|
3
|
+
*/
|
|
4
|
+
export interface PushDataItem {
|
|
5
|
+
pushDataId: string;
|
|
6
|
+
dataDetail: string;
|
|
7
|
+
time: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 保存推送数据,返回 pushDataId
|
|
11
|
+
*/
|
|
12
|
+
export declare function savePushData(dataDetail: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* 根据 pushDataId 获取推送数据
|
|
15
|
+
*/
|
|
16
|
+
export declare function getPushDataById(pushDataId: string): Promise<PushDataItem | null>;
|
|
17
|
+
/**
|
|
18
|
+
* 搜索推送数据(支持关键词模糊匹配)
|
|
19
|
+
*/
|
|
20
|
+
export declare function searchPushData(keywords?: string): Promise<PushDataItem[]>;
|
|
21
|
+
/**
|
|
22
|
+
* 获取所有推送数据
|
|
23
|
+
*/
|
|
24
|
+
export declare function getAllPushData(): Promise<PushDataItem[]>;
|
|
25
|
+
/**
|
|
26
|
+
* 清空所有推送数据(用于测试或重置)
|
|
27
|
+
*/
|
|
28
|
+
export declare function clearAllPushData(): Promise<void>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// pushData 持久化管理器
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
import { logger } from "./logger.js";
|
|
6
|
+
const PUSHDATA_FILE = "/home/snadbox/.openclaw/pushData.json";
|
|
7
|
+
const MAX_PUSHDATA_ITEMS = 1000; // 最多保留 1000 条记录
|
|
8
|
+
/**
|
|
9
|
+
* 格式化北京时间(UTC+8)
|
|
10
|
+
*/
|
|
11
|
+
function formatBeijingTime(date) {
|
|
12
|
+
// 转换为北京时间(UTC+8)
|
|
13
|
+
const beijingTime = new Date(date.getTime() + 8 * 60 * 60 * 1000);
|
|
14
|
+
const iso = beijingTime.toISOString(); // 2024-03-22T10:30:45.123Z
|
|
15
|
+
// 转换为 YYYYMMDD HHmmss 格式
|
|
16
|
+
return iso
|
|
17
|
+
.replace(/T/, ' ')
|
|
18
|
+
.replace(/\.\d+Z$/, '')
|
|
19
|
+
.replace(/-/g, '')
|
|
20
|
+
.replace(/:/g, '')
|
|
21
|
+
.substring(0, 15); // 取前15个字符:YYYYMMDD HHmmss
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 确保目录存在
|
|
25
|
+
*/
|
|
26
|
+
async function ensureDirectoryExists(filePath) {
|
|
27
|
+
const dir = path.dirname(filePath);
|
|
28
|
+
try {
|
|
29
|
+
await fs.mkdir(dir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logger.error(`[PushDataManager] Failed to create directory ${dir}:`, error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 读取 pushData 列表
|
|
37
|
+
*/
|
|
38
|
+
async function readPushDataList() {
|
|
39
|
+
try {
|
|
40
|
+
await ensureDirectoryExists(PUSHDATA_FILE);
|
|
41
|
+
const content = await fs.readFile(PUSHDATA_FILE, "utf-8");
|
|
42
|
+
const list = JSON.parse(content);
|
|
43
|
+
if (!Array.isArray(list)) {
|
|
44
|
+
logger.warn(`[PushDataManager] pushData.json is not an array, returning empty array`);
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
return list;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error.code === "ENOENT") {
|
|
51
|
+
// 文件不存在,返回空数组
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
logger.error(`[PushDataManager] Failed to read pushData:`, error);
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 写入 pushData 列表
|
|
60
|
+
*/
|
|
61
|
+
async function writePushDataList(list) {
|
|
62
|
+
try {
|
|
63
|
+
await ensureDirectoryExists(PUSHDATA_FILE);
|
|
64
|
+
// 限制数据条数,保留最近的 MAX_PUSHDATA_ITEMS 条
|
|
65
|
+
const limitedList = list.slice(-MAX_PUSHDATA_ITEMS);
|
|
66
|
+
await fs.writeFile(PUSHDATA_FILE, JSON.stringify(limitedList, null, 2), "utf-8");
|
|
67
|
+
if (list.length > MAX_PUSHDATA_ITEMS) {
|
|
68
|
+
logger.log(`[PushDataManager] Trimmed pushData list from ${list.length} to ${limitedList.length} items`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
logger.error(`[PushDataManager] Failed to write pushData:`, error);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 保存推送数据,返回 pushDataId
|
|
78
|
+
*/
|
|
79
|
+
export async function savePushData(dataDetail) {
|
|
80
|
+
const pushDataId = randomUUID();
|
|
81
|
+
const time = formatBeijingTime(new Date());
|
|
82
|
+
const item = {
|
|
83
|
+
pushDataId,
|
|
84
|
+
dataDetail,
|
|
85
|
+
time,
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
const list = await readPushDataList();
|
|
89
|
+
list.push(item);
|
|
90
|
+
await writePushDataList(list);
|
|
91
|
+
logger.log(`[PushDataManager] ✅ Saved pushData`);
|
|
92
|
+
logger.log(`[PushDataManager] - pushDataId: ${pushDataId}`);
|
|
93
|
+
logger.log(`[PushDataManager] - time: ${time}`);
|
|
94
|
+
logger.log(`[PushDataManager] - dataDetail length: ${dataDetail.length} chars`);
|
|
95
|
+
logger.log(`[PushDataManager] - Total items: ${list.length}`);
|
|
96
|
+
return pushDataId;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
logger.error(`[PushDataManager] Failed to save pushData:`, error);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 根据 pushDataId 获取推送数据
|
|
105
|
+
*/
|
|
106
|
+
export async function getPushDataById(pushDataId) {
|
|
107
|
+
try {
|
|
108
|
+
const list = await readPushDataList();
|
|
109
|
+
const item = list.find((item) => item.pushDataId === pushDataId);
|
|
110
|
+
if (item) {
|
|
111
|
+
logger.log(`[PushDataManager] Found pushData: ${pushDataId}`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
logger.warn(`[PushDataManager] pushData not found: ${pushDataId}`);
|
|
115
|
+
}
|
|
116
|
+
return item || null;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
logger.error(`[PushDataManager] Failed to get pushData by id:`, error);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 搜索推送数据(支持关键词模糊匹配)
|
|
125
|
+
*/
|
|
126
|
+
export async function searchPushData(keywords) {
|
|
127
|
+
try {
|
|
128
|
+
const list = await readPushDataList();
|
|
129
|
+
if (!keywords || keywords.trim() === "") {
|
|
130
|
+
// 无关键词,返回所有数据
|
|
131
|
+
logger.log(`[PushDataManager] Search with no keywords, returning all ${list.length} items`);
|
|
132
|
+
return list;
|
|
133
|
+
}
|
|
134
|
+
// 关键词模糊匹配(在 dataDetail 和 pushDataId 中搜索)
|
|
135
|
+
const lowerKeywords = keywords.toLowerCase();
|
|
136
|
+
const results = list.filter((item) => item.dataDetail.toLowerCase().includes(lowerKeywords) ||
|
|
137
|
+
item.pushDataId.toLowerCase().includes(lowerKeywords));
|
|
138
|
+
logger.log(`[PushDataManager] Search with keywords "${keywords}": found ${results.length} items`);
|
|
139
|
+
return results;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
logger.error(`[PushDataManager] Failed to search pushData:`, error);
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 获取所有推送数据
|
|
148
|
+
*/
|
|
149
|
+
export async function getAllPushData() {
|
|
150
|
+
try {
|
|
151
|
+
const list = await readPushDataList();
|
|
152
|
+
logger.log(`[PushDataManager] Retrieved ${list.length} pushData items`);
|
|
153
|
+
return list;
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
logger.error(`[PushDataManager] Failed to get all pushData:`, error);
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 清空所有推送数据(用于测试或重置)
|
|
162
|
+
*/
|
|
163
|
+
export async function clearAllPushData() {
|
|
164
|
+
try {
|
|
165
|
+
await writePushDataList([]);
|
|
166
|
+
logger.log(`[PushDataManager] Cleared all pushData`);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
logger.error(`[PushDataManager] Failed to clear pushData:`, error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 添加新的 pushId(去重)
|
|
3
|
+
*/
|
|
4
|
+
export declare function addPushId(pushId: string): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* 获取所有 pushId
|
|
7
|
+
*/
|
|
8
|
+
export declare function getAllPushIds(): Promise<string[]>;
|
|
9
|
+
/**
|
|
10
|
+
* 清空所有 pushId(用于测试或重置)
|
|
11
|
+
*/
|
|
12
|
+
export declare function clearAllPushIds(): Promise<void>;
|