@ynhcj/xiaoyi-channel 0.0.165-beta → 0.0.167-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/cron-query-handler.d.ts +1 -11
- package/dist/src/cron-query-handler.js +87 -0
- package/dist/src/formatter.d.ts +1 -0
- package/dist/src/formatter.js +8 -5
- package/dist/src/reply-dispatcher.js +43 -13
- package/dist/src/tools/check-plugin-privilege-tool.d.ts +6 -0
- package/dist/src/tools/check-plugin-privilege-tool.js +180 -0
- package/dist/src/tools/create-all-tools.js +2 -0
- package/dist/src/tools/display-a2ui-card-tool.js +9 -6
- package/package.json +1 -1
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
export type CronQueryAction = "list" | "status" | "runs" | "add" | "update" | "remove" | "run";
|
|
2
|
-
export interface CronQueryEventContext {
|
|
3
|
-
action: CronQueryAction;
|
|
4
|
-
jobId?: string;
|
|
5
|
-
params?: Record<string, unknown>;
|
|
6
|
-
/** Original A2A message fields for routing the response. */
|
|
7
|
-
sessionId?: string;
|
|
8
|
-
taskId?: string;
|
|
9
|
-
messageId?: string;
|
|
10
|
-
}
|
|
11
1
|
/**
|
|
12
2
|
* Handle a cron-query-event.
|
|
13
3
|
*
|
|
14
4
|
* Calls the Gateway cron RPC and sends the result back through sendCommand
|
|
15
5
|
* as a System.CronQuery command with the full result object in payload.ans.
|
|
16
6
|
*/
|
|
17
|
-
export declare function handleCronQueryEvent(context:
|
|
7
|
+
export declare function handleCronQueryEvent(context: any, cfg: any): Promise<void>;
|
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
// result back to the client via sendCommand as a System.CronQuery
|
|
5
5
|
// command with the result in payload.ans.
|
|
6
6
|
import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
7
|
+
import * as os from "os";
|
|
7
8
|
import { sendCommand } from "./formatter.js";
|
|
8
9
|
import { resolveXYConfig } from "./config.js";
|
|
9
10
|
import { logger } from "./utils/logger.js";
|
|
11
|
+
import { readFileSync, readdirSync } from "fs";
|
|
12
|
+
import { join } from "path";
|
|
10
13
|
const GATEWAY_TIMEOUT_MS = 60_000;
|
|
11
14
|
/**
|
|
12
15
|
* Handle a cron-query-event.
|
|
@@ -54,6 +57,9 @@ export async function handleCronQueryEvent(context, cfg) {
|
|
|
54
57
|
...params,
|
|
55
58
|
});
|
|
56
59
|
break;
|
|
60
|
+
case "queryTimeList":
|
|
61
|
+
result = await queryTimeListLocal();
|
|
62
|
+
break;
|
|
57
63
|
default:
|
|
58
64
|
error = `Unknown action: ${context.action}`;
|
|
59
65
|
logger.error(`[CRON-QUERY] ${error}`);
|
|
@@ -99,3 +105,84 @@ export async function handleCronQueryEvent(context, cfg) {
|
|
|
99
105
|
logger.warn(`[CRON-QUERY] Missing cfg/sessionId/taskId/messageId, skipping sendCommand`);
|
|
100
106
|
}
|
|
101
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Read local cron folder directly (bypassing openclaw RPC) and return
|
|
110
|
+
* run records from the last 7 days, grouped by date and sorted by time.
|
|
111
|
+
*
|
|
112
|
+
* Data sources:
|
|
113
|
+
* - state/cron/jobs.json → job id → name mapping
|
|
114
|
+
* - state/cron/runs/*.jsonl → run records (one JSON per line)
|
|
115
|
+
*
|
|
116
|
+
* Return format:
|
|
117
|
+
* [ { "YYYY-MM-DD": [ { run record with .name }, ... ] }, ... ]
|
|
118
|
+
*/
|
|
119
|
+
async function queryTimeListLocal() {
|
|
120
|
+
const cronDir = join(os.homedir(), ".openclaw", "cron");
|
|
121
|
+
const jobsPath = join(cronDir, "jobs.json");
|
|
122
|
+
const runsDir = join(cronDir, "runs");
|
|
123
|
+
// 1. Build jobId → name map from jobs.json
|
|
124
|
+
const jobNameMap = {};
|
|
125
|
+
try {
|
|
126
|
+
const jobsRaw = readFileSync(jobsPath, "utf-8");
|
|
127
|
+
const jobsData = JSON.parse(jobsRaw);
|
|
128
|
+
for (const job of jobsData.jobs || []) {
|
|
129
|
+
jobNameMap[job.id] = job.name || job.id;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
logger.error(`[CRON-QUERY] Failed to read jobs.json: ${err.message}`);
|
|
134
|
+
}
|
|
135
|
+
// 2. Read all run files, collect runs within last 7 days
|
|
136
|
+
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
137
|
+
const allRuns = [];
|
|
138
|
+
let files = [];
|
|
139
|
+
try {
|
|
140
|
+
files = readdirSync(runsDir);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
files = [];
|
|
144
|
+
}
|
|
145
|
+
for (const file of files) {
|
|
146
|
+
if (!file.endsWith(".jsonl"))
|
|
147
|
+
continue;
|
|
148
|
+
try {
|
|
149
|
+
const content = readFileSync(join(runsDir, file), "utf-8");
|
|
150
|
+
const lines = content.trim().split("\n");
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
if (!line.trim())
|
|
153
|
+
continue;
|
|
154
|
+
try {
|
|
155
|
+
const run = JSON.parse(line);
|
|
156
|
+
if (run.ts && run.ts >= sevenDaysAgo) {
|
|
157
|
+
run.name = jobNameMap[run.jobId] || run.jobId || "";
|
|
158
|
+
allRuns.push(run);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// skip malformed line
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
logger.error(`[CRON-QUERY] Failed to read run file ${file}: ${err.message}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// 3. Sort by ts ascending
|
|
171
|
+
allRuns.sort((a, b) => a.ts - b.ts);
|
|
172
|
+
// 4. Group by date (YYYY-MM-DD in local time)
|
|
173
|
+
const grouped = new Map();
|
|
174
|
+
for (const run of allRuns) {
|
|
175
|
+
const d = new Date(run.ts);
|
|
176
|
+
const label = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
177
|
+
if (!grouped.has(label)) {
|
|
178
|
+
grouped.set(label, []);
|
|
179
|
+
}
|
|
180
|
+
grouped.get(label).push(run);
|
|
181
|
+
}
|
|
182
|
+
// 5. Convert to ordered array of single-key objects
|
|
183
|
+
const result = [];
|
|
184
|
+
for (const [date, runs] of grouped) {
|
|
185
|
+
result.push({ [date]: runs });
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
package/dist/src/formatter.d.ts
CHANGED
package/dist/src/formatter.js
CHANGED
|
@@ -42,7 +42,7 @@ function buildTextPreview(text) {
|
|
|
42
42
|
* Send an A2A artifact update response.
|
|
43
43
|
*/
|
|
44
44
|
export async function sendA2AResponse(params) {
|
|
45
|
-
const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage } = params;
|
|
45
|
+
const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage, log: shouldLog = true } = params;
|
|
46
46
|
const log = logger.withContext(sessionId, taskId);
|
|
47
47
|
// 审批桥接:将 OpenClaw 的审批提示翻译成用户友好的确认文案
|
|
48
48
|
const bridgedText = text === undefined ? text : rewriteOutboundApprovalText(sessionId, text);
|
|
@@ -97,11 +97,14 @@ export async function sendA2AResponse(params) {
|
|
|
97
97
|
taskId,
|
|
98
98
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
99
99
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
if (shouldLog) {
|
|
101
|
+
const redactedText = redactSensitiveText(bridgedText ?? "");
|
|
102
|
+
log.log(`[A2A_RESPONSE] Sending artifact-update, append=${append}, final=${final}, text=${buildTextPreview(redactedText)}, files=${files?.length ?? 0}, sensitive=${containsSensitiveInfo(bridgedText ?? "")}`);
|
|
103
|
+
}
|
|
103
104
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
104
|
-
|
|
105
|
+
if (shouldLog) {
|
|
106
|
+
log.log(`[A2A_RESPONSE] Message sent successfully`);
|
|
107
|
+
}
|
|
105
108
|
}
|
|
106
109
|
/**
|
|
107
110
|
* Send an A2A artifact-update with reasoningText part.
|
|
@@ -172,18 +172,23 @@ export function createXYReplyDispatcher(params) {
|
|
|
172
172
|
scopedLog().log(`[DELIVER SKIP] Empty text, skipping`);
|
|
173
173
|
return;
|
|
174
174
|
}
|
|
175
|
+
// 🔑 如果 onPartialReply 已经流式发送过文本,deliver 不再重复发送
|
|
176
|
+
if (hasSentResponse) {
|
|
177
|
+
scopedLog().log(`[DELIVER SKIP] Already sent via onPartialReply`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
175
180
|
accumulatedText += text;
|
|
176
181
|
hasSentResponse = true;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
await sendReasoningTextUpdate({
|
|
182
|
+
// 🔑 使用动态taskId发送A2A响应(流式append)
|
|
183
|
+
await sendA2AResponse({
|
|
180
184
|
config,
|
|
181
185
|
sessionId,
|
|
182
186
|
taskId: currentTaskId,
|
|
183
187
|
messageId: currentMessageId,
|
|
184
188
|
text,
|
|
189
|
+
append: true,
|
|
190
|
+
final: false,
|
|
185
191
|
});
|
|
186
|
-
scopedLog().log(`[DELIVER] Sent deliver text as reasoningText update`);
|
|
187
192
|
}
|
|
188
193
|
catch (deliverError) {
|
|
189
194
|
scopedLog().error(`Failed to deliver message:`, deliverError);
|
|
@@ -251,18 +256,18 @@ export function createXYReplyDispatcher(params) {
|
|
|
251
256
|
state: "completed",
|
|
252
257
|
});
|
|
253
258
|
scopedLog().log(`[ON-IDLE] Sent completion status update`);
|
|
254
|
-
// 🔑 使用动态taskId
|
|
259
|
+
// 🔑 使用动态taskId发送最终响应(空字符串表示流结束)
|
|
255
260
|
await sendA2AResponse({
|
|
256
261
|
config,
|
|
257
262
|
sessionId,
|
|
258
263
|
taskId: currentTaskId,
|
|
259
264
|
messageId: currentMessageId,
|
|
260
|
-
text:
|
|
265
|
+
text: "",
|
|
261
266
|
append: false,
|
|
262
267
|
final: true,
|
|
263
268
|
});
|
|
264
269
|
finalSent = true;
|
|
265
|
-
scopedLog().log(`[ON-IDLE] Sent final response`);
|
|
270
|
+
scopedLog().log(`[ON-IDLE] Sent final response (empty, stream end)`);
|
|
266
271
|
}
|
|
267
272
|
catch (err) {
|
|
268
273
|
scopedLog().error(`[ON-IDLE] Failed to send final response:`, err);
|
|
@@ -389,10 +394,30 @@ export function createXYReplyDispatcher(params) {
|
|
|
389
394
|
if (steerState.steered) {
|
|
390
395
|
return;
|
|
391
396
|
}
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
//
|
|
397
|
+
const currentTaskId = getActiveTaskId();
|
|
398
|
+
const currentMessageId = getActiveMessageId();
|
|
399
|
+
let text = payload.text ?? "";
|
|
400
|
+
// Strip "Reasoning:" prefix that some reasoning models add to their thinking output
|
|
401
|
+
const lines = text.split(/\r?\n/);
|
|
402
|
+
if (lines[0]?.trim() === "Reasoning:") {
|
|
403
|
+
text = lines.slice(1).join("\n").trim();
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
if (text.length > 0) {
|
|
407
|
+
// 🔑 将模型真实的thinking/reasoning内容通过reasoningText转发
|
|
408
|
+
await sendReasoningTextUpdate({
|
|
409
|
+
config,
|
|
410
|
+
sessionId,
|
|
411
|
+
taskId: currentTaskId,
|
|
412
|
+
messageId: currentMessageId,
|
|
413
|
+
text,
|
|
414
|
+
append: false,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (err) {
|
|
419
|
+
scopedLog().error(`[REASONING-STREAM] Failed to send reasoning text:`, err);
|
|
420
|
+
}
|
|
396
421
|
},
|
|
397
422
|
onPartialReply: async (payload) => {
|
|
398
423
|
// 🔑 steered dispatch不发送partial reply(让主dispatcher处理)
|
|
@@ -404,18 +429,23 @@ export function createXYReplyDispatcher(params) {
|
|
|
404
429
|
const text = payload.text ?? "";
|
|
405
430
|
try {
|
|
406
431
|
if (text.length > 0) {
|
|
407
|
-
|
|
432
|
+
accumulatedText += text;
|
|
433
|
+
hasSentResponse = true;
|
|
434
|
+
// 🔑 流式文本通过 A2A text 通道发送(而非 reasoningText)
|
|
435
|
+
await sendA2AResponse({
|
|
408
436
|
config,
|
|
409
437
|
sessionId,
|
|
410
438
|
taskId: currentTaskId,
|
|
411
439
|
messageId: currentMessageId,
|
|
412
440
|
text,
|
|
413
441
|
append: false,
|
|
442
|
+
final: false,
|
|
443
|
+
log: false,
|
|
414
444
|
});
|
|
415
445
|
}
|
|
416
446
|
}
|
|
417
447
|
catch (err) {
|
|
418
|
-
scopedLog().error(`[PARTIAL
|
|
448
|
+
scopedLog().error(`[PARTIAL-REPLY] Failed to send partial reply:`, err);
|
|
419
449
|
}
|
|
420
450
|
},
|
|
421
451
|
},
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// Check plugin privilege tool implementation
|
|
2
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
3
|
+
import { sendCommand } from "../formatter.js";
|
|
4
|
+
import { getCurrentTaskId } from "../task-manager.js";
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Mapping from intent name to required permission IDs.
|
|
8
|
+
*/
|
|
9
|
+
const INTENT_PERMISSION_MAP = {
|
|
10
|
+
GetCurrentLocation: ["ohos.permission.LOCATION", "ohos.permission.APPROXIMATELY_LOCATION"],
|
|
11
|
+
SearchCalendarEvent: ["ohos.permission.READ_WHOLE_CALENDAR"],
|
|
12
|
+
CreateCalendarEvent: ["ohos.permission.WRITE_WHOLE_CALENDAR"],
|
|
13
|
+
DeleteCalendarEvent: ["ohos.permission.WRITE_WHOLE_CALENDAR"],
|
|
14
|
+
ModifyCalendarEvent: ["ohos.permission.WRITE_WHOLE_CALENDAR"],
|
|
15
|
+
SearchNote: ["ohos.permission.READ_NOTE"],
|
|
16
|
+
CreateNote: ["ohos.permission.WRITE_NOTE"],
|
|
17
|
+
ModifyNote: ["ohos.permission.WRITE_NOTE"],
|
|
18
|
+
SearchContactLocal: ["ohos.permission.READ_CONTACTS"],
|
|
19
|
+
SearchPhotoVideo: ["ohos.permission.READ_IMAGEVIDEO"],
|
|
20
|
+
SaveMediaToGallery: ["ohos.permission.WRITE_IMAGEVIDEO"],
|
|
21
|
+
SearchFile: ["ohos.permission.FILE_ACCESS_MANAGER"],
|
|
22
|
+
SaveFileToFileManager: ["ohos.permission.FILE_SAVE_MANAGER"],
|
|
23
|
+
SearchAlarm: ["ohos.permission.READ_ALARM"],
|
|
24
|
+
CreateAlarm: ["ohos.permission.WRITE_ALARM"],
|
|
25
|
+
ModifyAlarm: ["ohos.permission.WRITE_ALARM"],
|
|
26
|
+
DeleteAlarm: ["ohos.permission.WRITE_ALARM"],
|
|
27
|
+
SearchMessage: ["ohos.permission.READ_SMS"],
|
|
28
|
+
SendShortMessage: ["ohos.permission.SEND_MESSAGES"],
|
|
29
|
+
StartCall: ["ohos.permission.PLACE_CALL"],
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* XY check plugin privilege tool - checks user authorization for device-side tools
|
|
33
|
+
* used in scheduled tasks.
|
|
34
|
+
*/
|
|
35
|
+
export function createCheckPluginPrivilegeTool(ctx) {
|
|
36
|
+
const { config, sessionId, taskId, messageId } = ctx;
|
|
37
|
+
return {
|
|
38
|
+
name: "check_plugin_privilege",
|
|
39
|
+
label: "Check Plugin Privilege",
|
|
40
|
+
description: "定时任务权限检查工具。" +
|
|
41
|
+
"【使用场景】仅在创建定时任务时使用,严禁在其他场景调用。当识别到定时任务中需要使用用户设备侧工具时,必须调用此工具进行权限检查。" +
|
|
42
|
+
"【调用前提】调用此工具前,必须确认用户定时任务中提到的工具在当前模型可使用的工具列表中存在。如果当前工具列表中不存在符合用户诉求的工具定义,则不要调用此工具,而是直接告知用户当前设备不支持该功能。" +
|
|
43
|
+
"【支持的意图名称及对应权限】" +
|
|
44
|
+
"GetCurrentLocation(获取用户位置), " +
|
|
45
|
+
"SearchCalendarEvent(搜索用户日程), " +
|
|
46
|
+
"CreateCalendarEvent(新建用户日程), " +
|
|
47
|
+
"DeleteCalendarEvent(删除用户日程), " +
|
|
48
|
+
"ModifyCalendarEvent(修改用户日程), " +
|
|
49
|
+
"SearchNote(搜索用户备忘录), " +
|
|
50
|
+
"CreateNote(新建用户备忘录), " +
|
|
51
|
+
"ModifyNote(修改用户备忘录), " +
|
|
52
|
+
"SearchContactLocal(搜索用户联系人), " +
|
|
53
|
+
"SearchPhotoVideo(搜索用户图库照片或视频), " +
|
|
54
|
+
"SaveMediaToGallery(保存图片/视频到图库), " +
|
|
55
|
+
"SearchFile(搜索用户文件管理里面的文件), " +
|
|
56
|
+
"SaveFileToFileManager(保存文件到文件管理), " +
|
|
57
|
+
"SearchAlarm(搜索闹钟), " +
|
|
58
|
+
"CreateAlarm(新建闹钟), " +
|
|
59
|
+
"ModifyAlarm(修改闹钟), " +
|
|
60
|
+
"DeleteAlarm(删除闹钟), " +
|
|
61
|
+
"SearchMessage(搜索短信), " +
|
|
62
|
+
"SendShortMessage(发送短信), " +
|
|
63
|
+
"StartCall(打电话)。" +
|
|
64
|
+
"【多次调用】如果用户的定时任务指令中涉及多个端侧工具,则依次分别调用此工具检查每个工具的权限。如果调用超时失败,最多重试一次。" +
|
|
65
|
+
"【回复约束】如果工具返回没有授权或其他报错,只需要完整描述没有授权或其他报错内容即可,不需要主动给用户提供解决方案。",
|
|
66
|
+
parameters: {
|
|
67
|
+
type: "object",
|
|
68
|
+
properties: {
|
|
69
|
+
checkIntentName: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "需要检查权限的意图名称,必须是支持的意图名称之一,例如:GetCurrentLocation、SearchCalendarEvent、CreateCalendarEvent 等。",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
required: ["checkIntentName"],
|
|
75
|
+
},
|
|
76
|
+
async execute(toolCallId, params) {
|
|
77
|
+
const { checkIntentName } = params;
|
|
78
|
+
// Look up permission IDs for the given intent name
|
|
79
|
+
const permissionId = INTENT_PERMISSION_MAP[checkIntentName];
|
|
80
|
+
if (!permissionId) {
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: `不支持的工具意图名称: ${checkIntentName}。请确认该意图名称在支持列表中。`,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Get WebSocket manager
|
|
91
|
+
const wsManager = getXYWebSocketManager(config);
|
|
92
|
+
// Build CheckPlugInPrivilege command
|
|
93
|
+
const command = {
|
|
94
|
+
header: {
|
|
95
|
+
namespace: "Common",
|
|
96
|
+
name: "Action",
|
|
97
|
+
},
|
|
98
|
+
payload: {
|
|
99
|
+
cardParam: {},
|
|
100
|
+
executeParam: {
|
|
101
|
+
achieveType: "INTENT",
|
|
102
|
+
actionResponse: true,
|
|
103
|
+
bundleName: "com.huawei.hmos.vassistant",
|
|
104
|
+
dimension: "",
|
|
105
|
+
executeMode: "background",
|
|
106
|
+
intentName: "CheckPlugInPrivilege",
|
|
107
|
+
intentParam: {
|
|
108
|
+
checkIntentName: "CheckPluginPrivilege",
|
|
109
|
+
permissionId,
|
|
110
|
+
},
|
|
111
|
+
needUnlock: false,
|
|
112
|
+
permissionId: [],
|
|
113
|
+
timeOut: 5,
|
|
114
|
+
},
|
|
115
|
+
needUploadResult: true,
|
|
116
|
+
pageControlRelated: false,
|
|
117
|
+
responses: [
|
|
118
|
+
{
|
|
119
|
+
displayText: "",
|
|
120
|
+
resultCode: "",
|
|
121
|
+
ttsText: "",
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
// Send command and wait for response (60 second timeout)
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
const timeout = setTimeout(() => {
|
|
129
|
+
wsManager.off("data-event", handler);
|
|
130
|
+
logger.error("超时: 检查插件权限超时(60秒)", { toolCallId, checkIntentName });
|
|
131
|
+
reject(new Error(`检查插件权限超时(60秒), intentName: ${checkIntentName}`));
|
|
132
|
+
}, 60000);
|
|
133
|
+
// Listen for data events from WebSocket
|
|
134
|
+
const handler = (event) => {
|
|
135
|
+
if (event.intentName === "CheckPlugInPrivilege") {
|
|
136
|
+
clearTimeout(timeout);
|
|
137
|
+
wsManager.off("data-event", handler);
|
|
138
|
+
if (event.status === "success" && event.outputs) {
|
|
139
|
+
resolve({
|
|
140
|
+
content: [
|
|
141
|
+
{
|
|
142
|
+
type: "text",
|
|
143
|
+
text: JSON.stringify(event.outputs),
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
resolve({
|
|
150
|
+
content: [
|
|
151
|
+
{
|
|
152
|
+
type: "text",
|
|
153
|
+
text: JSON.stringify(event.outputs),
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
// Register event handler
|
|
161
|
+
wsManager.on("data-event", handler);
|
|
162
|
+
// Send the command
|
|
163
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
164
|
+
sendCommand({
|
|
165
|
+
config,
|
|
166
|
+
sessionId,
|
|
167
|
+
taskId: currentTaskId,
|
|
168
|
+
messageId,
|
|
169
|
+
command,
|
|
170
|
+
toolCallId,
|
|
171
|
+
}).then(() => {
|
|
172
|
+
}).catch((error) => {
|
|
173
|
+
clearTimeout(timeout);
|
|
174
|
+
wsManager.off("data-event", handler);
|
|
175
|
+
reject(error);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -19,6 +19,7 @@ import { createAgentAsSkillTool } from "./agent-as-skill-tool.js";
|
|
|
19
19
|
import { createDiscoverCrossDevicesTool } from "./discover-cross-devices-tool.js";
|
|
20
20
|
import { createSendCrossDeviceTaskTool } from "./send-cross-device-task-tool.js";
|
|
21
21
|
import { createDisplayA2UICardTool } from "./display-a2ui-card-tool.js";
|
|
22
|
+
import { createCheckPluginPrivilegeTool } from "./check-plugin-privilege-tool.js";
|
|
22
23
|
import { logger } from "../utils/logger.js";
|
|
23
24
|
/**
|
|
24
25
|
* Create all XY channel tools for the given session context.
|
|
@@ -54,5 +55,6 @@ export function createAllTools(ctx) {
|
|
|
54
55
|
createSaveSelfEvolutionSkillTool(ctx),
|
|
55
56
|
createLoginTokenTool(ctx),
|
|
56
57
|
createAgentAsSkillTool(ctx),
|
|
58
|
+
createCheckPluginPrivilegeTool(ctx),
|
|
57
59
|
];
|
|
58
60
|
}
|
|
@@ -8,8 +8,8 @@ class ToolInputError extends Error {
|
|
|
8
8
|
this.name = "ToolInputError";
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
return !!value && typeof value === "object"
|
|
11
|
+
function isJsonObjectOrArray(value) {
|
|
12
|
+
return !!value && typeof value === "object";
|
|
13
13
|
}
|
|
14
14
|
export function createDisplayA2UICardTool(ctx) {
|
|
15
15
|
const { config, sessionId, taskId, messageId } = ctx;
|
|
@@ -25,8 +25,11 @@ export function createDisplayA2UICardTool(ctx) {
|
|
|
25
25
|
description: "A2UI card 的唯一标识。",
|
|
26
26
|
},
|
|
27
27
|
cardData: {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
oneOf: [
|
|
29
|
+
{ type: "object" },
|
|
30
|
+
{ type: "array" },
|
|
31
|
+
],
|
|
32
|
+
description: "A2UI card 渲染所需的 JSON 对象或 JSON 数组,由模型根据 MCP 工具返回结果填充。",
|
|
30
33
|
},
|
|
31
34
|
},
|
|
32
35
|
required: ["cardId", "cardData"],
|
|
@@ -37,8 +40,8 @@ export function createDisplayA2UICardTool(ctx) {
|
|
|
37
40
|
if (!cardId) {
|
|
38
41
|
throw new ToolInputError("缺少必填参数: cardId");
|
|
39
42
|
}
|
|
40
|
-
if (!
|
|
41
|
-
throw new ToolInputError("缺少必填参数: cardData
|
|
43
|
+
if (!isJsonObjectOrArray(cardData)) {
|
|
44
|
+
throw new ToolInputError("缺少必填参数: cardData,且必须是 JSON 对象或 JSON 数组");
|
|
42
45
|
}
|
|
43
46
|
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
44
47
|
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|