@ynhcj/xiaoyi-channel 0.0.159-beta → 0.0.159-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 +59 -5
- package/dist/src/cron-query-handler.d.ts +1 -11
- package/dist/src/cron-query-handler.js +89 -2
- package/dist/src/cspl/call_api.d.ts +1 -1
- package/dist/src/cspl/call_api.js +2 -2
- package/dist/src/cspl/config.js +30 -10
- package/dist/src/cspl/constants.d.ts +3 -0
- package/dist/src/cspl/constants.js +5 -0
- package/dist/src/cspl/sentinel_hook.js +26 -7
- package/dist/src/cspl/utils.d.ts +9 -3
- package/dist/src/cspl/utils.js +17 -11
- package/dist/src/file-upload.d.ts +5 -0
- package/dist/src/file-upload.js +102 -0
- package/dist/src/formatter.d.ts +30 -0
- package/dist/src/formatter.js +65 -10
- package/dist/src/monitor.js +35 -23
- package/dist/src/parser.d.ts +6 -0
- package/dist/src/parser.js +47 -1
- package/dist/src/provider.js +51 -17
- package/dist/src/reply-dispatcher.js +67 -21
- package/dist/src/self-evolution-handler.d.ts +1 -1
- package/dist/src/self-evolution-handler.js +12 -1
- package/dist/src/tools/calendar-tool.js +1 -1
- package/dist/src/tools/call-device-tool.js +0 -3
- package/dist/src/tools/call-phone-tool.js +1 -1
- package/dist/src/tools/check-plugin-privilege-tool.d.ts +6 -0
- package/dist/src/tools/check-plugin-privilege-tool.js +182 -0
- package/dist/src/tools/create-alarm-tool.js +1 -1
- package/dist/src/tools/create-all-tools.js +8 -8
- package/dist/src/tools/delete-alarm-tool.js +1 -1
- package/dist/src/tools/device-tool-map.js +1 -0
- package/dist/src/tools/discover-cross-devices-tool.js +1 -1
- package/dist/src/tools/display-a2ui-card-tool.d.ts +2 -0
- package/dist/src/tools/display-a2ui-card-tool.js +85 -0
- package/dist/src/tools/get-device-file-tool-schema.js +2 -3
- package/dist/src/tools/location-tool.js +1 -1
- package/dist/src/tools/modify-alarm-tool.js +1 -1
- package/dist/src/tools/modify-note-tool.js +1 -1
- package/dist/src/tools/note-tool.js +1 -1
- package/dist/src/tools/query-app-message-tool.js +1 -1
- package/dist/src/tools/query-memory-data-tool.js +1 -1
- package/dist/src/tools/query-todo-task-tool.js +1 -1
- package/dist/src/tools/save-file-to-phone-tool.js +1 -1
- package/dist/src/tools/save-media-to-gallery-tool.js +1 -1
- package/dist/src/tools/schema-tool-factory.js +1 -1
- package/dist/src/tools/search-alarm-tool.js +1 -1
- package/dist/src/tools/search-calendar-tool.js +1 -1
- package/dist/src/tools/search-contact-tool.js +1 -1
- package/dist/src/tools/search-email-tool.js +1 -1
- package/dist/src/tools/search-file-tool.js +5 -10
- package/dist/src/tools/search-note-tool.js +1 -1
- package/dist/src/tools/search-photo-gallery-tool.js +1 -1
- package/dist/src/tools/send-cross-device-task-tool.js +18 -22
- package/dist/src/tools/send-email-tool.js +1 -1
- package/dist/src/tools/send-file-to-user-tool.d.ts +1 -1
- package/dist/src/tools/send-file-to-user-tool.js +35 -6
- package/dist/src/tools/send-html-card-tool.d.ts +7 -0
- package/dist/src/tools/send-html-card-tool.js +113 -0
- package/dist/src/tools/session-manager.d.ts +6 -2
- package/dist/src/tools/session-manager.js +42 -7
- package/dist/src/tools/upload-file-tool.d.ts +1 -1
- package/dist/src/tools/upload-file-tool.js +3 -17
- package/dist/src/tools/upload-photo-tool.js +1 -1
- package/dist/src/tools/xiaoyi-add-collection-tool.js +2 -2
- package/dist/src/tools/xiaoyi-collection-tool.js +1 -1
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +1 -1
- package/dist/src/tools/xiaoyi-gui-tool.js +6 -1
- package/dist/src/types.d.ts +10 -3
- package/dist/src/websocket.js +29 -8
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import { getXYRuntime } from "./runtime.js";
|
|
|
2
2
|
import { sendA2AResponse, sendStatusUpdate, sendReasoningTextUpdate, sendCommand } from "./formatter.js";
|
|
3
3
|
import { resolveXYConfig } from "./config.js";
|
|
4
4
|
import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
5
|
-
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
5
|
+
import { clearRunCrossTaskSentFiles, getCurrentSessionContext } from "./tools/session-manager.js";
|
|
6
6
|
import fs from "fs/promises";
|
|
7
7
|
import path from "path";
|
|
8
8
|
import { logger } from "./utils/logger.js";
|
|
@@ -23,7 +23,7 @@ function buildDistributionStatusCommand(context) {
|
|
|
23
23
|
},
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
function buildCrossTaskExecuteResultCommand(code, message,
|
|
26
|
+
function buildCrossTaskExecuteResultCommand(code, message, sentFiles = []) {
|
|
27
27
|
return {
|
|
28
28
|
header: {
|
|
29
29
|
namespace: "DistributionInteraction",
|
|
@@ -32,15 +32,15 @@ function buildCrossTaskExecuteResultCommand(code, message, fileUrls = []) {
|
|
|
32
32
|
payload: {
|
|
33
33
|
code,
|
|
34
34
|
message,
|
|
35
|
-
|
|
35
|
+
sentFiles,
|
|
36
36
|
},
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
async function sendRunCrossTaskResult(params) {
|
|
40
40
|
const { config, sessionId, taskId, messageId, context, resultCode, resultMessage } = params;
|
|
41
|
-
const
|
|
41
|
+
const sentFiles = Array.isArray(context.sentFiles) ? context.sentFiles : [];
|
|
42
42
|
const statusCommand = buildDistributionStatusCommand(context);
|
|
43
|
-
const resultCommand = buildCrossTaskExecuteResultCommand(resultCode, resultMessage,
|
|
43
|
+
const resultCommand = buildCrossTaskExecuteResultCommand(resultCode, resultMessage, sentFiles);
|
|
44
44
|
await sendCommand({
|
|
45
45
|
config,
|
|
46
46
|
sessionId,
|
|
@@ -48,7 +48,8 @@ async function sendRunCrossTaskResult(params) {
|
|
|
48
48
|
messageId,
|
|
49
49
|
commands: [statusCommand, resultCommand],
|
|
50
50
|
});
|
|
51
|
-
|
|
51
|
+
clearRunCrossTaskSentFiles(context);
|
|
52
|
+
logger.log(`${RUN_CROSS_TASK_LOG_TAG} sent cross-task result, sessionId=${sessionId}, taskId=${taskId}, code=${resultCode}, sentFileCount=${sentFiles.length}, clearedSentFileCount=${sentFiles.length}, messageLength=${resultMessage.length}`);
|
|
52
53
|
}
|
|
53
54
|
/**
|
|
54
55
|
* 清理 /tmp/xy_channel 目录中超过 24 小时的旧文件
|
|
@@ -115,6 +116,8 @@ export function createXYReplyDispatcher(params) {
|
|
|
115
116
|
let hasSentResponse = false;
|
|
116
117
|
let finalSent = false;
|
|
117
118
|
let accumulatedText = "";
|
|
119
|
+
let accumulatedReasoningHistory = "";
|
|
120
|
+
let lastReasoningText = "";
|
|
118
121
|
const initialRunCrossTaskContext = getCurrentSessionContext()?.runCrossTaskContext;
|
|
119
122
|
const getRunCrossTaskContext = () => {
|
|
120
123
|
return getCurrentSessionContext()?.runCrossTaskContext ?? initialRunCrossTaskContext;
|
|
@@ -171,18 +174,23 @@ export function createXYReplyDispatcher(params) {
|
|
|
171
174
|
scopedLog().log(`[DELIVER SKIP] Empty text, skipping`);
|
|
172
175
|
return;
|
|
173
176
|
}
|
|
177
|
+
// 🔑 如果 onPartialReply 已经流式发送过文本,deliver 不再重复发送
|
|
178
|
+
if (hasSentResponse) {
|
|
179
|
+
scopedLog().log(`[DELIVER SKIP] Already sent via onPartialReply`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
174
182
|
accumulatedText += text;
|
|
175
183
|
hasSentResponse = true;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
await sendReasoningTextUpdate({
|
|
184
|
+
// 🔑 使用动态taskId发送A2A响应(流式append)
|
|
185
|
+
await sendA2AResponse({
|
|
179
186
|
config,
|
|
180
187
|
sessionId,
|
|
181
188
|
taskId: currentTaskId,
|
|
182
189
|
messageId: currentMessageId,
|
|
183
190
|
text,
|
|
191
|
+
append: true,
|
|
192
|
+
final: false,
|
|
184
193
|
});
|
|
185
|
-
scopedLog().log(`[DELIVER] Sent deliver text as reasoningText update`);
|
|
186
194
|
}
|
|
187
195
|
catch (deliverError) {
|
|
188
196
|
scopedLog().error(`Failed to deliver message:`, deliverError);
|
|
@@ -250,18 +258,18 @@ export function createXYReplyDispatcher(params) {
|
|
|
250
258
|
state: "completed",
|
|
251
259
|
});
|
|
252
260
|
scopedLog().log(`[ON-IDLE] Sent completion status update`);
|
|
253
|
-
// 🔑 使用动态taskId
|
|
261
|
+
// 🔑 使用动态taskId发送最终响应(空字符串表示流结束)
|
|
254
262
|
await sendA2AResponse({
|
|
255
263
|
config,
|
|
256
264
|
sessionId,
|
|
257
265
|
taskId: currentTaskId,
|
|
258
266
|
messageId: currentMessageId,
|
|
259
|
-
text:
|
|
260
|
-
append:
|
|
267
|
+
text: "",
|
|
268
|
+
append: true,
|
|
261
269
|
final: true,
|
|
262
270
|
});
|
|
263
271
|
finalSent = true;
|
|
264
|
-
scopedLog().log(`[ON-IDLE] Sent final response`);
|
|
272
|
+
scopedLog().log(`[ON-IDLE] Sent final response (empty, stream end)`);
|
|
265
273
|
}
|
|
266
274
|
catch (err) {
|
|
267
275
|
scopedLog().error(`[ON-IDLE] Failed to send final response:`, err);
|
|
@@ -298,7 +306,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
298
306
|
taskId: currentTaskId,
|
|
299
307
|
messageId: currentMessageId,
|
|
300
308
|
text: "任务执行异常,请重试~",
|
|
301
|
-
append:
|
|
309
|
+
append: true,
|
|
302
310
|
final: true,
|
|
303
311
|
errorCode: 99921111,
|
|
304
312
|
errorMessage: "任务执行异常,请重试",
|
|
@@ -321,6 +329,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
321
329
|
dispatcher,
|
|
322
330
|
replyOptions: {
|
|
323
331
|
...replyOptions,
|
|
332
|
+
suppressToolErrorWarnings: true,
|
|
324
333
|
onModelSelected: prefixContext.onModelSelected,
|
|
325
334
|
onToolStart: async ({ name, phase }) => {
|
|
326
335
|
// 🔑 steered dispatch不发送tool状态(让主dispatcher处理)
|
|
@@ -387,10 +396,43 @@ export function createXYReplyDispatcher(params) {
|
|
|
387
396
|
if (steerState.steered) {
|
|
388
397
|
return;
|
|
389
398
|
}
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
//
|
|
399
|
+
const currentTaskId = getActiveTaskId();
|
|
400
|
+
const currentMessageId = getActiveMessageId();
|
|
401
|
+
let text = payload.text ?? "";
|
|
402
|
+
// Strip "Reasoning:" prefix that some reasoning models add to their thinking output
|
|
403
|
+
const lines = text.split(/\r?\n/);
|
|
404
|
+
if (lines[0]?.trim() === "Reasoning:") {
|
|
405
|
+
text = lines.slice(1).join("\n").trim();
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
if (text.length > 0) {
|
|
409
|
+
// 🔑 检测是否是新一轮思考:当前text比上一次短,或不以上次内容开头
|
|
410
|
+
const isNewRound = lastReasoningText.length > 0 &&
|
|
411
|
+
text.length < lastReasoningText.length;
|
|
412
|
+
if (isNewRound) {
|
|
413
|
+
// 将上一轮思考追加到历史
|
|
414
|
+
accumulatedReasoningHistory += (accumulatedReasoningHistory ? "\n\n" : "") + lastReasoningText;
|
|
415
|
+
}
|
|
416
|
+
// 更新当前轮最后一次text
|
|
417
|
+
lastReasoningText = text;
|
|
418
|
+
// 🔑 拼接历史 + 当前轮内容
|
|
419
|
+
const fullText = accumulatedReasoningHistory
|
|
420
|
+
? accumulatedReasoningHistory + "\n\n" + text
|
|
421
|
+
: text;
|
|
422
|
+
// 🔑 将模型真实的thinking/reasoning内容通过reasoningText转发
|
|
423
|
+
await sendReasoningTextUpdate({
|
|
424
|
+
config,
|
|
425
|
+
sessionId,
|
|
426
|
+
taskId: currentTaskId,
|
|
427
|
+
messageId: currentMessageId,
|
|
428
|
+
text: fullText,
|
|
429
|
+
append: false,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
catch (err) {
|
|
434
|
+
scopedLog().error(`[REASONING-STREAM] Failed to send reasoning text:`, err);
|
|
435
|
+
}
|
|
394
436
|
},
|
|
395
437
|
onPartialReply: async (payload) => {
|
|
396
438
|
// 🔑 steered dispatch不发送partial reply(让主dispatcher处理)
|
|
@@ -402,18 +444,22 @@ export function createXYReplyDispatcher(params) {
|
|
|
402
444
|
const text = payload.text ?? "";
|
|
403
445
|
try {
|
|
404
446
|
if (text.length > 0) {
|
|
405
|
-
|
|
447
|
+
accumulatedText += text;
|
|
448
|
+
hasSentResponse = true;
|
|
449
|
+
await sendA2AResponse({
|
|
406
450
|
config,
|
|
407
451
|
sessionId,
|
|
408
452
|
taskId: currentTaskId,
|
|
409
453
|
messageId: currentMessageId,
|
|
410
454
|
text,
|
|
411
455
|
append: false,
|
|
456
|
+
final: false,
|
|
457
|
+
log: false,
|
|
412
458
|
});
|
|
413
459
|
}
|
|
414
460
|
}
|
|
415
461
|
catch (err) {
|
|
416
|
-
scopedLog().error(`[PARTIAL
|
|
462
|
+
scopedLog().error(`[PARTIAL-REPLY] Failed to send partial reply:`, err);
|
|
417
463
|
}
|
|
418
464
|
},
|
|
419
465
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { XYWebSocketManager } from "./websocket.js";
|
|
2
|
-
export declare function handleSelfEvolutionEvent(context: any,
|
|
2
|
+
export declare function handleSelfEvolutionEvent(context: any, cfg: any): Promise<void>;
|
|
3
3
|
/**
|
|
4
4
|
* 读取 .xiaoyiruntime 中的 selfEvolutionState 并直接通过 wsManager 下发指令回复设备
|
|
5
5
|
* 参考trigger实现:直接使用当前已连接的 wsManager 发送消息,避免 getXYWebSocketManager 返回未连接实例
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "fs";
|
|
2
2
|
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import { resolveXYConfig } from "./config.js";
|
|
4
|
+
import { sendA2AResponse } from "./formatter.js";
|
|
3
5
|
import { logger } from "./utils/logger.js";
|
|
4
6
|
const XIAOYIRUNTIME_PATH = "/home/sandbox/.openclaw/.xiaoyiruntime";
|
|
5
|
-
export function handleSelfEvolutionEvent(context,
|
|
7
|
+
export async function handleSelfEvolutionEvent(context, cfg) {
|
|
6
8
|
try {
|
|
7
9
|
const state = context.event?.payload?.selfEvolutionState;
|
|
8
10
|
if (typeof state !== "string") {
|
|
9
11
|
logger.error("[SELF_EVOLUTION] invalid payload: missing selfEvolutionState");
|
|
10
12
|
return;
|
|
11
13
|
}
|
|
14
|
+
const sessionId = context.sessionId ?? "";
|
|
15
|
+
const taskId = context.taskId ?? sessionId;
|
|
16
|
+
const messageId = context.messageId ?? uuidv4();
|
|
17
|
+
const config = resolveXYConfig(cfg);
|
|
12
18
|
logger.log(`[SELF_EVOLUTION] received state: ${state}`);
|
|
13
19
|
let content;
|
|
14
20
|
try {
|
|
@@ -19,6 +25,8 @@ export function handleSelfEvolutionEvent(context, runtime) {
|
|
|
19
25
|
logger.log(`[SELF_EVOLUTION] ${XIAOYIRUNTIME_PATH} not found, creating new file`);
|
|
20
26
|
writeFileSync(XIAOYIRUNTIME_PATH, `selfEvolutionState=${state}\n`, "utf-8");
|
|
21
27
|
logger.log(`[SELF_EVOLUTION] wrote selfEvolutionState=${state}`);
|
|
28
|
+
await sendA2AResponse({ config, sessionId, taskId, messageId, text: "", append: false, final: true });
|
|
29
|
+
logger.log(`[SELF_EVOLUTION] Sent final response (empty, stream end)`);
|
|
22
30
|
return;
|
|
23
31
|
}
|
|
24
32
|
const lines = content.split("\n");
|
|
@@ -40,6 +48,9 @@ export function handleSelfEvolutionEvent(context, runtime) {
|
|
|
40
48
|
writeFileSync(XIAOYIRUNTIME_PATH, updated.join("\n"), "utf-8");
|
|
41
49
|
}
|
|
42
50
|
logger.log(`[SELF_EVOLUTION] updated selfEvolutionState=${state} in ${XIAOYIRUNTIME_PATH}`);
|
|
51
|
+
// Reply with final empty response to acknowledge the state update
|
|
52
|
+
await sendA2AResponse({ config, sessionId, taskId, messageId, text: "", append: false, final: true });
|
|
53
|
+
logger.log(`[SELF_EVOLUTION] Sent final response (empty, stream end)`);
|
|
43
54
|
}
|
|
44
55
|
catch (err) {
|
|
45
56
|
logger.error("[SELF_EVOLUTION] failed to handle event:", err);
|
|
@@ -22,7 +22,6 @@ import { createUploadFileTool } from "./upload-file-tool.js";
|
|
|
22
22
|
import { createSaveFileToPhoneTool } from "./save-file-to-phone-tool.js";
|
|
23
23
|
import { createSendEmailTool } from "./send-email-tool.js";
|
|
24
24
|
import { createSearchEmailTool } from "./search-email-tool.js";
|
|
25
|
-
import { createFindPcDevicesTool } from "./find-pc-devices-tool.js";
|
|
26
25
|
import { sendStatusUpdate } from "../formatter.js";
|
|
27
26
|
import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
|
|
28
27
|
/**
|
|
@@ -55,7 +54,6 @@ export function createCallDeviceTool(ctx) {
|
|
|
55
54
|
const searchFileTool = createSearchFileTool(ctx);
|
|
56
55
|
const saveFileToPhoneTool = createSaveFileToPhoneTool(ctx);
|
|
57
56
|
const searchEmailTool = createSearchEmailTool(ctx);
|
|
58
|
-
const findPcDevicesTool = createFindPcDevicesTool(ctx);
|
|
59
57
|
/**
|
|
60
58
|
* 端工具注册表 —— 按 name 索引所有可通过 call_device_tool 调度的工具。
|
|
61
59
|
*/
|
|
@@ -84,7 +82,6 @@ export function createCallDeviceTool(ctx) {
|
|
|
84
82
|
[saveFileToPhoneTool.name, saveFileToPhoneTool],
|
|
85
83
|
[sendEmailTool.name, sendEmailTool],
|
|
86
84
|
[searchEmailTool.name, searchEmailTool],
|
|
87
|
-
[findPcDevicesTool.name, findPcDevicesTool],
|
|
88
85
|
]);
|
|
89
86
|
return {
|
|
90
87
|
name: "call_device_tool",
|
|
@@ -0,0 +1,182 @@
|
|
|
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
|
+
"【使用约束1】只要是创建定时任务且涉及端插件的使用,则必须调用此工具检查权限" +
|
|
67
|
+
"【使用约束2】如果是定时任务执行过程中,禁止调用此工具,此工具仅在创建定时任务时按需调用",
|
|
68
|
+
parameters: {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: {
|
|
71
|
+
checkIntentName: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description: "需要检查权限的意图名称,必须是支持的意图名称之一,例如:GetCurrentLocation、SearchCalendarEvent、CreateCalendarEvent 等。",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
required: ["checkIntentName"],
|
|
77
|
+
},
|
|
78
|
+
async execute(toolCallId, params) {
|
|
79
|
+
const { checkIntentName } = params;
|
|
80
|
+
// Look up permission IDs for the given intent name
|
|
81
|
+
const permissionId = INTENT_PERMISSION_MAP[checkIntentName];
|
|
82
|
+
if (!permissionId) {
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: `不支持的工具意图名称: ${checkIntentName}。请确认该意图名称在支持列表中。`,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Get WebSocket manager
|
|
93
|
+
const wsManager = getXYWebSocketManager(config);
|
|
94
|
+
// Build CheckPlugInPrivilege command
|
|
95
|
+
const command = {
|
|
96
|
+
header: {
|
|
97
|
+
namespace: "Common",
|
|
98
|
+
name: "Action",
|
|
99
|
+
},
|
|
100
|
+
payload: {
|
|
101
|
+
cardParam: {},
|
|
102
|
+
executeParam: {
|
|
103
|
+
achieveType: "INTENT",
|
|
104
|
+
actionResponse: true,
|
|
105
|
+
bundleName: "com.huawei.hmos.vassistant",
|
|
106
|
+
dimension: "",
|
|
107
|
+
executeMode: "background",
|
|
108
|
+
intentName: "CheckPlugInPrivilege",
|
|
109
|
+
intentParam: {
|
|
110
|
+
checkIntentName,
|
|
111
|
+
permissionId,
|
|
112
|
+
},
|
|
113
|
+
needUnlock: false,
|
|
114
|
+
permissionId: [],
|
|
115
|
+
timeOut: 5,
|
|
116
|
+
},
|
|
117
|
+
needUploadResult: true,
|
|
118
|
+
pageControlRelated: false,
|
|
119
|
+
responses: [
|
|
120
|
+
{
|
|
121
|
+
displayText: "",
|
|
122
|
+
resultCode: "",
|
|
123
|
+
ttsText: "",
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
// Send command and wait for response (60 second timeout)
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const timeout = setTimeout(() => {
|
|
131
|
+
wsManager.off("data-event", handler);
|
|
132
|
+
logger.error("超时: 检查插件权限超时(60秒)", { toolCallId, checkIntentName });
|
|
133
|
+
reject(new Error(`检查插件权限超时(60秒), intentName: ${checkIntentName}`));
|
|
134
|
+
}, 60000);
|
|
135
|
+
// Listen for data events from WebSocket
|
|
136
|
+
const handler = (event) => {
|
|
137
|
+
if (event.intentName === "CheckPlugInPrivilege") {
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
wsManager.off("data-event", handler);
|
|
140
|
+
if (event.status === "success" && event.outputs) {
|
|
141
|
+
resolve({
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: "text",
|
|
145
|
+
text: JSON.stringify(event.outputs),
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
resolve({
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: "text",
|
|
155
|
+
text: JSON.stringify(event.outputs),
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
// Register event handler
|
|
163
|
+
wsManager.on("data-event", handler);
|
|
164
|
+
// Send the command
|
|
165
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
166
|
+
sendCommand({
|
|
167
|
+
config,
|
|
168
|
+
sessionId,
|
|
169
|
+
taskId: currentTaskId,
|
|
170
|
+
messageId,
|
|
171
|
+
command,
|
|
172
|
+
toolCallId,
|
|
173
|
+
}).then(() => {
|
|
174
|
+
}).catch((error) => {
|
|
175
|
+
clearTimeout(timeout);
|
|
176
|
+
wsManager.off("data-event", handler);
|
|
177
|
+
reject(error);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createLocationTool } from "./location-tool.js";
|
|
2
2
|
import { createXiaoyiGuiTool } from "./xiaoyi-gui-tool.js";
|
|
3
3
|
import { createSendFileToUserTool } from "./send-file-to-user-tool.js";
|
|
4
|
+
import { createSendHtmlCardTool } from "./send-html-card-tool.js";
|
|
4
5
|
import { viewPushResultTool } from "./view-push-result-tool.js";
|
|
5
6
|
import { createImageReadingTool } from "./image-reading-tool.js";
|
|
6
7
|
import { timestampToUtc8Tool } from "./timestamp-to-utc8-tool.js";
|
|
@@ -13,12 +14,9 @@ import { createGetPhotoToolSchemaTool } from "./get-photo-tool-schema.js";
|
|
|
13
14
|
import { createGetDeviceFileToolSchemaTool } from "./get-device-file-tool-schema.js";
|
|
14
15
|
import { createGetAlarmToolSchemaTool } from "./get-alarm-tool-schema.js";
|
|
15
16
|
import { createGetCollectionToolSchemaTool } from "./get-collection-tool-schema.js";
|
|
16
|
-
import { createGetEmailToolSchemaTool } from "./get-email-tool-schema.js";
|
|
17
|
+
// import { createGetEmailToolSchemaTool } from "./get-email-tool-schema.js";
|
|
17
18
|
import { createLoginTokenTool } from "./login-token-tool.js";
|
|
18
19
|
import { createAgentAsSkillTool } from "./agent-as-skill-tool.js";
|
|
19
|
-
import { createFindPcDevicesTool } from "./find-pc-devices-tool.js";
|
|
20
|
-
import { createDiscoverCrossDevicesTool } from "./discover-cross-devices-tool.js";
|
|
21
|
-
import { createSendCrossDeviceTaskTool } from "./send-cross-device-task-tool.js";
|
|
22
20
|
import { logger } from "../utils/logger.js";
|
|
23
21
|
/**
|
|
24
22
|
* Create all XY channel tools for the given session context.
|
|
@@ -34,8 +32,9 @@ export function createAllTools(ctx) {
|
|
|
34
32
|
logger.log(`[CREATE-ALL-TOOLS] creating tools`);
|
|
35
33
|
return [
|
|
36
34
|
createLocationTool(ctx),
|
|
37
|
-
createDiscoverCrossDevicesTool(ctx),
|
|
38
|
-
createSendCrossDeviceTaskTool(ctx),
|
|
35
|
+
// createDiscoverCrossDevicesTool(ctx),
|
|
36
|
+
// createSendCrossDeviceTaskTool(ctx),
|
|
37
|
+
// createDisplayA2UICardTool(ctx),
|
|
39
38
|
createCallDeviceTool(ctx),
|
|
40
39
|
createGetNoteToolSchemaTool(ctx),
|
|
41
40
|
createGetCalendarToolSchemaTool(ctx),
|
|
@@ -46,13 +45,14 @@ export function createAllTools(ctx) {
|
|
|
46
45
|
createGetAlarmToolSchemaTool(ctx),
|
|
47
46
|
createGetCollectionToolSchemaTool(ctx),
|
|
48
47
|
createSendFileToUserTool(ctx),
|
|
49
|
-
|
|
48
|
+
createSendHtmlCardTool(ctx),
|
|
49
|
+
// createGetEmailToolSchemaTool(ctx),
|
|
50
50
|
viewPushResultTool,
|
|
51
51
|
createImageReadingTool(ctx),
|
|
52
52
|
timestampToUtc8Tool,
|
|
53
53
|
createSaveSelfEvolutionSkillTool(ctx),
|
|
54
54
|
createLoginTokenTool(ctx),
|
|
55
55
|
createAgentAsSkillTool(ctx),
|
|
56
|
-
|
|
56
|
+
// createCheckPluginPrivilegeTool(ctx),
|
|
57
57
|
];
|
|
58
58
|
}
|
|
@@ -106,7 +106,7 @@ export function createDiscoverCrossDevicesTool(ctx) {
|
|
|
106
106
|
|
|
107
107
|
当用户明确表达要从另一台设备获取、查找、使用或操作内容时,必须优先调用本工具,例如从 PC、电脑、平板、手机等设备获取文件或查找内容。
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
本工具只做设备发现和目标设备推荐,不会读取副设备文件内容,不会上传文件,也不会真正下发跨端执行任务。下发跨端执行任务需要使用SendCrossDeviceTaskTool`,
|
|
110
110
|
parameters: {
|
|
111
111
|
type: "object",
|
|
112
112
|
properties: {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { sendCommand } from "../formatter.js";
|
|
2
|
+
import { getCurrentMessageId, getCurrentTaskId } from "../task-manager.js";
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
class ToolInputError extends Error {
|
|
5
|
+
status = 400;
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "ToolInputError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function isJsonObjectOrArray(value) {
|
|
12
|
+
return !!value && typeof value === "object";
|
|
13
|
+
}
|
|
14
|
+
export function createDisplayA2UICardTool(ctx) {
|
|
15
|
+
const { config, sessionId, taskId, messageId } = ctx;
|
|
16
|
+
return {
|
|
17
|
+
name: "displayA2UICard",
|
|
18
|
+
label: "Display A2UI Card",
|
|
19
|
+
description: "当模型根据 MCP 工具返回结果判断需要向端侧下发 A2UI card 时调用。参数 cardId 和 cardData 由模型根据 MCP 工具返回结果传入,本工具只负责下发卡片展示指令。",
|
|
20
|
+
parameters: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
cardId: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "A2UI card 的唯一标识。",
|
|
26
|
+
},
|
|
27
|
+
cardData: {
|
|
28
|
+
oneOf: [
|
|
29
|
+
{ type: "object" },
|
|
30
|
+
{ type: "array" },
|
|
31
|
+
],
|
|
32
|
+
description: "A2UI card 渲染所需的 JSON 对象或 JSON 数组,由模型根据 MCP 工具返回结果填充。",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: ["cardId", "cardData"],
|
|
36
|
+
},
|
|
37
|
+
async execute(toolCallId, params) {
|
|
38
|
+
const cardId = typeof params?.cardId === "string" ? params.cardId.trim() : "";
|
|
39
|
+
const cardData = params?.cardData;
|
|
40
|
+
if (!cardId) {
|
|
41
|
+
throw new ToolInputError("缺少必填参数: cardId");
|
|
42
|
+
}
|
|
43
|
+
if (!isJsonObjectOrArray(cardData)) {
|
|
44
|
+
throw new ToolInputError("缺少必填参数: cardData,且必须是 JSON 对象或 JSON 数组");
|
|
45
|
+
}
|
|
46
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
47
|
+
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
48
|
+
const command = {
|
|
49
|
+
header: {
|
|
50
|
+
namespace: "Common",
|
|
51
|
+
name: "DisplayFACard",
|
|
52
|
+
},
|
|
53
|
+
payload: {
|
|
54
|
+
isA2ui: true,
|
|
55
|
+
a2uiParam: {
|
|
56
|
+
cardId,
|
|
57
|
+
cardData,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
logger.log(`[DISPLAY-A2UI-CARD] sending card, cardId=${cardId}`);
|
|
62
|
+
await sendCommand({
|
|
63
|
+
config,
|
|
64
|
+
sessionId,
|
|
65
|
+
taskId: currentTaskId,
|
|
66
|
+
messageId: currentMessageId,
|
|
67
|
+
command,
|
|
68
|
+
toolCallId,
|
|
69
|
+
});
|
|
70
|
+
logger.log(`[DISPLAY-A2UI-CARD] card sent successfully, cardId=${cardId}`);
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: JSON.stringify({
|
|
76
|
+
success: true,
|
|
77
|
+
cardId,
|
|
78
|
+
message: "A2UI card display command sent successfully.",
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|