@ynhcj/xiaoyi-channel 0.0.152-beta → 0.0.154-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/index.js +23 -0
- package/dist/src/bot.js +9 -1
- package/dist/src/channel.js +59 -5
- package/dist/src/cron-command.d.ts +16 -0
- package/dist/src/cron-command.js +64 -0
- package/dist/src/formatter.d.ts +11 -1
- package/dist/src/formatter.js +22 -2
- package/dist/src/parser.d.ts +2 -1
- package/dist/src/parser.js +25 -0
- package/dist/src/reply-dispatcher.js +73 -1
- package/dist/src/tools/agent-as-skill-tool.js +1 -0
- package/dist/src/tools/calendar-tool.js +1 -0
- package/dist/src/tools/call-phone-tool.js +1 -0
- package/dist/src/tools/create-alarm-tool.js +1 -0
- package/dist/src/tools/create-all-tools.js +4 -0
- package/dist/src/tools/delete-alarm-tool.js +1 -0
- package/dist/src/tools/device-tool-map.d.ts +1 -1
- package/dist/src/tools/device-tool-map.js +11 -5
- package/dist/src/tools/discover-cross-devices-tool.d.ts +2 -0
- package/dist/src/tools/discover-cross-devices-tool.js +235 -0
- package/dist/src/tools/find-pc-devices-tool.js +1 -0
- package/dist/src/tools/location-tool.js +1 -0
- package/dist/src/tools/modify-alarm-tool.js +1 -0
- package/dist/src/tools/modify-note-tool.js +1 -0
- package/dist/src/tools/note-tool.js +1 -0
- package/dist/src/tools/query-app-message-tool.js +3 -2
- package/dist/src/tools/query-memory-data-tool.js +3 -2
- package/dist/src/tools/query-todo-task-tool.js +3 -2
- package/dist/src/tools/save-file-to-phone-tool.js +1 -0
- package/dist/src/tools/save-media-to-gallery-tool.js +1 -0
- package/dist/src/tools/search-alarm-tool.js +1 -0
- package/dist/src/tools/search-calendar-tool.js +1 -0
- package/dist/src/tools/search-contact-tool.js +1 -0
- package/dist/src/tools/search-email-tool.js +3 -2
- package/dist/src/tools/search-file-tool.js +1 -0
- package/dist/src/tools/search-message-tool.js +1 -0
- package/dist/src/tools/search-note-tool.js +1 -0
- package/dist/src/tools/search-photo-gallery-tool.js +3 -2
- package/dist/src/tools/send-cross-device-task-tool.d.ts +2 -0
- package/dist/src/tools/send-cross-device-task-tool.js +303 -0
- package/dist/src/tools/send-email-tool.js +3 -2
- package/dist/src/tools/send-message-tool.js +1 -0
- package/dist/src/tools/session-manager.d.ts +13 -1
- package/dist/src/tools/session-manager.js +38 -0
- package/dist/src/tools/upload-file-tool.d.ts +1 -1
- package/dist/src/tools/upload-file-tool.js +8 -2
- package/dist/src/tools/upload-photo-tool.js +3 -2
- package/dist/src/tools/xiaoyi-add-collection-tool.js +1 -0
- package/dist/src/tools/xiaoyi-collection-tool.js +1 -0
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +1 -0
- package/dist/src/tools/xiaoyi-gui-tool.js +1 -0
- package/dist/src/types.d.ts +17 -0
- package/dist/src/websocket.d.ts +3 -0
- package/dist/src/websocket.js +168 -15
- package/package.json +2 -1
package/dist/src/websocket.js
CHANGED
|
@@ -5,6 +5,9 @@ import { EventEmitter } from "events";
|
|
|
5
5
|
import { logger } from "./utils/logger.js";
|
|
6
6
|
import { HeartbeatManager } from "./heartbeat.js";
|
|
7
7
|
import { MessageQueue } from "./message-queue.js";
|
|
8
|
+
import { v4 as uuidv4 } from "uuid";
|
|
9
|
+
const RUN_CROSS_TASK_LOG_TAG = "[RunCrossTask]";
|
|
10
|
+
const RUN_CROSS_TASK_QUERY_PREFIX = `# 跨设备协作接收模式<br/><br/>你当前正在接收来自其他设备的协作请求。请注意以下角色转换规则:<br/><br/>## 角色转换规则<br/><br/>- 指令中的"我" = 发送请求的远程用户<br/>- 你是执行协作任务的本地智能体<br/>- 任务完成后结果会自动回传给请求来源设备<br/><br/>## 核心执行规则<br/><br/>### ✅ 正确行为<br/>1. **识别本机任务**:当指令提到你所在的设备类型(PC/手机/平板),理解为"我自己"<br/>2. **本地执行**:直接使用本地工具完成任务,不要转发<br/>3. **结果回传**:执行完成后,结果会通过软总线自动回传给请求来源设备<br/><br/>### <span class="emoji emoji2716"></span> 禁止行为<br/>1. 禁止再次调用 \`send_cross_device_task\`(你已经是目标设备)<br/>2. 禁止设备澄清(指令已明确指定目标设备)<br/>3. 禁止无限循环(只能执行或回复,不能转发)<br/><br/>## 📁 文件操作规范(核心)<br/><br/>### 强制使用 search_file 的场景<br/>**以下场景必须先使用 \`search_file\` 工具确认文件路径:**<br/><br/>1. **指令包含设备关键词**:PC、电脑、手机、平板、Pad、笔记本等<br/>2. **涉及文件操作**:读取、编辑、删除、移动、复制、查找文件<br/><br/>### 执行流程<br/>\`\`\`<br/>收到文件操作指令<br/> ↓<br/>检测设备关键词(PC/电脑/手机/平板/Pad等)<br/> ↓<br/>使用 search_file 搜索文件 ← 必须步骤<br/> ↓<br/>确认文件实际路径<br/> ↓<br/>执行文件操作<br/> ↓<br/>返回结果<br/>\`\`\`<br/><br/>### 禁止行为<br/>- <span class="emoji emoji2716"></span> 禁止猜测文件路径<br/>- <span class="emoji emoji2716"></span> 禁止假设文件位置<br/>- <span class="emoji emoji2716"></span> 禁止跳过 search_file 步骤<br/><br/>## 示例<br/><br/>### 示例1:文件操作<br/>**指令**:"帮我到PC上下载昨天晚上写的PPT"<br/><br/>**执行流程**:<br/>1. ✅ 检测到"PC" → 使用 \`search_file\` 搜索 "*.ppt" 或 "*.pptx"<br/>2. 确认文件路径(如:D:\\Documents\\报告.pptx)<br/>3. 执行下载操作<br/><br/>### 示例2:文件编辑<br/>**指令**:"帮我修改电脑上的配置文件config.json"<br/><br/>**执行流程**:<br/>1. ✅ 检测到"电脑" → 使用 \`search_file\` 搜索 "config.json"<br/>2. 确认文件路径(如:C:\\Project\\config.json)<br/>3. 读取并修改文件<br/><br/>### 示例3:文件查找<br/>**指令**:"在平板上找一下我的PDF文档"<br/><br/>**执行流程**:<br/>1. ✅ 检测到"平板" → 使用 \`search_file\` 搜索 "*.pdf"<br/>2. 列出搜索结果供用户选择<br/><br/>## 判断流程<br/><br/>\`\`\`<br/>收到协作指令<br/> ↓<br/>检查目标设备<br/> ↓<br/>目标设备 == 本机?<br/> ↓<br/>是 → 本地执行(禁止send_cross_device_task)<br/> ↓<br/> 涉及文件? → 先用search_file确认路径<br/> ↓<br/>否 → 检查是否需要转发<br/> ↓<br/>需要转发 → 调用send_cross_device_task<br/>不需要 → 回复"无法处理"<br/>\`\`\``;
|
|
8
11
|
/**
|
|
9
12
|
* Manages single WebSocket connection to XY server.
|
|
10
13
|
*
|
|
@@ -359,6 +362,123 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
359
362
|
heartbeat.start();
|
|
360
363
|
this.heartbeat = heartbeat;
|
|
361
364
|
}
|
|
365
|
+
toUploadExeDataEvent(item) {
|
|
366
|
+
const outputs = item?.payload?.outputs ?? {};
|
|
367
|
+
const payloadIntentName = typeof item?.payload?.intentName === "string" ? item.payload.intentName : "";
|
|
368
|
+
const outputsIntentName = typeof outputs.intentName === "string" ? outputs.intentName : "";
|
|
369
|
+
const resolvedIntentName = payloadIntentName || outputsIntentName;
|
|
370
|
+
const isUploadExeResult = item?.header?.namespace === "Common" &&
|
|
371
|
+
item?.header?.name === "UploadExeResult" &&
|
|
372
|
+
resolvedIntentName.length > 0;
|
|
373
|
+
if (!isUploadExeResult) {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
this.log(`[XY] [GetPCDeviceList] received UploadExeResult event, intentName=${resolvedIntentName}`);
|
|
377
|
+
const code = outputs?.code;
|
|
378
|
+
const status = code === undefined || String(code) === "0" ? "success" : "failed";
|
|
379
|
+
const dataEvent = {
|
|
380
|
+
intentName: resolvedIntentName,
|
|
381
|
+
outputs,
|
|
382
|
+
status,
|
|
383
|
+
};
|
|
384
|
+
if (resolvedIntentName !== "SearchAllDeviceInfo") {
|
|
385
|
+
this.log(`[XY] normalized UploadExeResult data-event, intentName=${resolvedIntentName}, status=${status}`);
|
|
386
|
+
}
|
|
387
|
+
return dataEvent;
|
|
388
|
+
}
|
|
389
|
+
toCrossDeviceTaskResultEvent(item, sessionId) {
|
|
390
|
+
if (item?.header?.namespace !== "DistributionInteraction" || item?.header?.name !== "CrossTaskExecuteResult") {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
const code = item?.payload?.code === undefined ? "" : String(item.payload.code);
|
|
394
|
+
const message = typeof item?.payload?.message === "string" ? item.payload.message : "";
|
|
395
|
+
const fileUrls = Array.isArray(item?.payload?.fileUrls)
|
|
396
|
+
? item.payload.fileUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
397
|
+
: [];
|
|
398
|
+
const status = code === "0" ? "success" : "failed";
|
|
399
|
+
const event = {
|
|
400
|
+
sessionId,
|
|
401
|
+
code,
|
|
402
|
+
message,
|
|
403
|
+
fileUrls,
|
|
404
|
+
status,
|
|
405
|
+
rawEvent: item,
|
|
406
|
+
};
|
|
407
|
+
return event;
|
|
408
|
+
}
|
|
409
|
+
toRunCrossTaskA2ARequest(parsed, fallbackSessionId, fallbackTaskId) {
|
|
410
|
+
const networkId = typeof parsed?.networkId === "string" ? parsed.networkId.trim() : "";
|
|
411
|
+
if (!networkId) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
const originalParts = Array.isArray(parsed?.params?.message?.parts)
|
|
415
|
+
? parsed.params.message.parts
|
|
416
|
+
: [];
|
|
417
|
+
const hasTextQuery = originalParts.some((part) => part?.kind === "text" && typeof part?.text === "string" && part.text.trim().length > 0);
|
|
418
|
+
if (!hasTextQuery) {
|
|
419
|
+
this.log(`${RUN_CROSS_TASK_LOG_TAG} top-level networkId found but text query is empty`);
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
let hasPrependedCrossTaskPrompt = false;
|
|
423
|
+
const crossTaskParts = originalParts.map((part) => {
|
|
424
|
+
if (!hasPrependedCrossTaskPrompt &&
|
|
425
|
+
part?.kind === "text" &&
|
|
426
|
+
typeof part?.text === "string" &&
|
|
427
|
+
part.text.trim().length > 0) {
|
|
428
|
+
hasPrependedCrossTaskPrompt = true;
|
|
429
|
+
return {
|
|
430
|
+
...part,
|
|
431
|
+
text: `${RUN_CROSS_TASK_QUERY_PREFIX}\n${part.text}`,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return part;
|
|
435
|
+
});
|
|
436
|
+
const topLevelSessionId = typeof parsed?.sessionId === "string" ? parsed.sessionId : "";
|
|
437
|
+
const topLevelAgentId = typeof parsed?.agentId === "string" ? parsed.agentId : "";
|
|
438
|
+
const sessionId = topLevelSessionId || parsed?.params?.sessionId || fallbackSessionId || networkId;
|
|
439
|
+
const taskId = parsed?.params?.id || fallbackTaskId || parsed?.id || uuidv4();
|
|
440
|
+
const messageId = parsed?.id || parsed?.messageId || uuidv4();
|
|
441
|
+
const runCrossTaskContext = {
|
|
442
|
+
agentId: topLevelAgentId,
|
|
443
|
+
sessionId: topLevelSessionId,
|
|
444
|
+
networkId,
|
|
445
|
+
isDistributed: true,
|
|
446
|
+
isSupportAgent: true,
|
|
447
|
+
fileUrls: [],
|
|
448
|
+
rawContext: parsed,
|
|
449
|
+
};
|
|
450
|
+
const request = {
|
|
451
|
+
jsonrpc: "2.0",
|
|
452
|
+
method: "message/stream",
|
|
453
|
+
id: messageId,
|
|
454
|
+
params: {
|
|
455
|
+
id: taskId,
|
|
456
|
+
sessionId,
|
|
457
|
+
agentLoginSessionId: "",
|
|
458
|
+
message: {
|
|
459
|
+
role: "user",
|
|
460
|
+
parts: [
|
|
461
|
+
...crossTaskParts,
|
|
462
|
+
{
|
|
463
|
+
kind: "data",
|
|
464
|
+
data: {
|
|
465
|
+
runCrossTaskContext,
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
],
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
this.log(`${RUN_CROSS_TASK_LOG_TAG} normalized PC cross-task query to A2A request`, {
|
|
473
|
+
agentId: topLevelAgentId,
|
|
474
|
+
sessionId,
|
|
475
|
+
networkId,
|
|
476
|
+
taskId,
|
|
477
|
+
messageId,
|
|
478
|
+
prependedPrompt: hasPrependedCrossTaskPrompt,
|
|
479
|
+
});
|
|
480
|
+
return request;
|
|
481
|
+
}
|
|
362
482
|
/**
|
|
363
483
|
* Handle incoming message from server.
|
|
364
484
|
*/
|
|
@@ -373,6 +493,23 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
373
493
|
? logger.withContext(sessionId, taskId)
|
|
374
494
|
: { log: (msg, ...args) => logger.log(msg, ...args) };
|
|
375
495
|
log.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} characters`);
|
|
496
|
+
// Handle direct cross-task requests (top-level networkId)
|
|
497
|
+
const directRunCrossTaskRequest = this.toRunCrossTaskA2ARequest(parsed);
|
|
498
|
+
if (directRunCrossTaskRequest) {
|
|
499
|
+
this.emit("message", directRunCrossTaskRequest, directRunCrossTaskRequest.params.sessionId);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
// Handle top-level events array (cross-device task results)
|
|
503
|
+
if (Array.isArray(parsed.events)) {
|
|
504
|
+
const eventSessionId = parsed.session?.sessionId || parsed.sessionId;
|
|
505
|
+
for (const item of parsed.events) {
|
|
506
|
+
const crossDeviceTaskResult = this.toCrossDeviceTaskResultEvent(item, eventSessionId ?? "");
|
|
507
|
+
if (crossDeviceTaskResult) {
|
|
508
|
+
this.emit("cross-device-task-result", crossDeviceTaskResult);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
376
513
|
// 提取并打印消息内容(只显示 text,data 只打印提示)
|
|
377
514
|
const parts = parsed.params?.message?.parts;
|
|
378
515
|
if (parts && Array.isArray(parts) && parts.length > 0) {
|
|
@@ -422,15 +559,15 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
422
559
|
}
|
|
423
560
|
log.log(`[XY] Processing ${events.length} events from data.events`);
|
|
424
561
|
for (const item of events) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
status: "success",
|
|
430
|
-
};
|
|
431
|
-
log.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
|
|
562
|
+
const dataEvent = this.toUploadExeDataEvent(item);
|
|
563
|
+
const crossDeviceTaskResult = this.toCrossDeviceTaskResultEvent(item, sessionId);
|
|
564
|
+
if (dataEvent) {
|
|
565
|
+
log.log(`[XY] Emitting data-event, intentName: ${dataEvent.intentName}, status: ${dataEvent.status}, size: ${JSON.stringify(dataEvent).length} bytes`);
|
|
432
566
|
this.emit("data-event", dataEvent);
|
|
433
567
|
}
|
|
568
|
+
else if (crossDeviceTaskResult) {
|
|
569
|
+
this.emit("cross-device-task-result", crossDeviceTaskResult);
|
|
570
|
+
}
|
|
434
571
|
else if (item.header?.namespace === "ClawAgent" && item.header?.name === "InvokeJarvisGUIAgentResponse") {
|
|
435
572
|
log.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
|
|
436
573
|
this.emit("gui-agent-response", item);
|
|
@@ -490,7 +627,23 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
490
627
|
if (inboundMsg.msgType === "data") {
|
|
491
628
|
log.log("[XY] Processing data message");
|
|
492
629
|
try {
|
|
493
|
-
const
|
|
630
|
+
const parsedDetail = JSON.parse(inboundMsg.msgDetail);
|
|
631
|
+
const wrappedRunCrossTaskRequest = this.toRunCrossTaskA2ARequest(parsedDetail, inboundMsg.sessionId, inboundMsg.taskId);
|
|
632
|
+
if (wrappedRunCrossTaskRequest) {
|
|
633
|
+
this.emit("message", wrappedRunCrossTaskRequest, wrappedRunCrossTaskRequest.params.sessionId);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (Array.isArray(parsedDetail.events)) {
|
|
637
|
+
const eventSessionId = parsedDetail.session?.sessionId || inboundMsg.sessionId || parsedDetail.sessionId;
|
|
638
|
+
for (const item of parsedDetail.events) {
|
|
639
|
+
const crossDeviceTaskResult = this.toCrossDeviceTaskResultEvent(item, eventSessionId ?? "");
|
|
640
|
+
if (crossDeviceTaskResult) {
|
|
641
|
+
this.emit("cross-device-task-result", crossDeviceTaskResult);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const a2aRequest = parsedDetail;
|
|
494
647
|
const dataParts = a2aRequest.params?.message?.parts?.filter((p) => p.kind === "data");
|
|
495
648
|
if (dataParts && dataParts.length > 0) {
|
|
496
649
|
for (const dataPart of dataParts) {
|
|
@@ -501,15 +654,15 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
501
654
|
}
|
|
502
655
|
log.log(`[XY] Processing ${events.length} events from data.events`);
|
|
503
656
|
for (const item of events) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
status: "success",
|
|
509
|
-
};
|
|
510
|
-
log.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
|
|
657
|
+
const dataEvent = this.toUploadExeDataEvent(item);
|
|
658
|
+
const crossDeviceTaskResult = this.toCrossDeviceTaskResultEvent(item, inboundMsg.sessionId || a2aRequest.params?.sessionId);
|
|
659
|
+
if (dataEvent) {
|
|
660
|
+
log.log(`[XY] Emitting data-event, intentName: ${dataEvent.intentName}, status: ${dataEvent.status}, size: ${JSON.stringify(dataEvent).length} bytes`);
|
|
511
661
|
this.emit("data-event", dataEvent);
|
|
512
662
|
}
|
|
663
|
+
else if (crossDeviceTaskResult) {
|
|
664
|
+
this.emit("cross-device-task-result", crossDeviceTaskResult);
|
|
665
|
+
}
|
|
513
666
|
else if (item.header?.namespace === "ClawAgent" && item.header?.name === "InvokeJarvisGUIAgentResponse") {
|
|
514
667
|
log.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
|
|
515
668
|
this.emit("gui-agent-response", item);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ynhcj/xiaoyi-channel",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.154-beta",
|
|
4
4
|
"description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"@types/node": "^20.8.0",
|
|
67
67
|
"@types/uuid": "^9.0.5",
|
|
68
68
|
"@types/ws": "^8.5.8",
|
|
69
|
+
"openclaw": "^2026.5.7",
|
|
69
70
|
"typescript": "^5.9.2"
|
|
70
71
|
}
|
|
71
72
|
}
|