@ynhcj/xiaoyi-channel 0.0.26-beta → 0.0.28-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 +13 -3
- package/dist/src/channel.js +11 -1
- package/dist/src/outbound.js +88 -76
- package/dist/src/tools/calendar-tool.js +2 -2
- package/dist/src/tools/call-phone-tool.d.ts +5 -0
- package/dist/src/tools/call-phone-tool.js +183 -0
- package/dist/src/tools/create-alarm-tool.d.ts +7 -0
- package/dist/src/tools/create-alarm-tool.js +444 -0
- package/dist/src/tools/delete-alarm-tool.d.ts +11 -0
- package/dist/src/tools/delete-alarm-tool.js +238 -0
- package/dist/src/tools/location-tool.js +2 -2
- package/dist/src/tools/modify-alarm-tool.d.ts +9 -0
- package/dist/src/tools/modify-alarm-tool.js +474 -0
- package/dist/src/tools/modify-note-tool.js +2 -2
- package/dist/src/tools/note-tool.js +2 -2
- package/dist/src/tools/search-alarm-tool.d.ts +8 -0
- package/dist/src/tools/search-alarm-tool.js +389 -0
- package/dist/src/tools/search-calendar-tool.js +2 -2
- package/dist/src/tools/search-contact-tool.js +2 -2
- package/dist/src/tools/search-file-tool.d.ts +5 -0
- package/dist/src/tools/search-file-tool.js +173 -0
- package/dist/src/tools/search-message-tool.d.ts +5 -0
- package/dist/src/tools/search-message-tool.js +173 -0
- package/dist/src/tools/search-note-tool.js +2 -2
- package/dist/src/tools/search-photo-gallery-tool.js +2 -2
- package/dist/src/tools/send-file-to-user-tool.d.ts +5 -0
- package/dist/src/tools/send-file-to-user-tool.js +290 -0
- package/dist/src/tools/send-message-tool.d.ts +5 -0
- package/dist/src/tools/send-message-tool.js +189 -0
- package/dist/src/tools/session-manager.d.ts +12 -0
- package/dist/src/tools/session-manager.js +33 -0
- package/dist/src/tools/upload-file-tool.d.ts +13 -0
- package/dist/src/tools/upload-file-tool.js +265 -0
- package/dist/src/tools/upload-photo-tool.js +2 -2
- package/dist/src/tools/xiaoyi-gui-tool.js +2 -2
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -4,7 +4,7 @@ import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId
|
|
|
4
4
|
import { downloadFilesFromParts } from "./file-download.js";
|
|
5
5
|
import { resolveXYConfig } from "./config.js";
|
|
6
6
|
import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse } from "./formatter.js";
|
|
7
|
-
import { registerSession, unregisterSession } from "./tools/session-manager.js";
|
|
7
|
+
import { registerSession, unregisterSession, runWithSessionContext } from "./tools/session-manager.js";
|
|
8
8
|
import { configManager } from "./utils/config-manager.js";
|
|
9
9
|
import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActiveTask, } from "./task-manager.js";
|
|
10
10
|
/**
|
|
@@ -200,6 +200,14 @@ export async function handleXYMessage(params) {
|
|
|
200
200
|
log(`xy: dispatching to agent (session=${parsed.sessionId})`);
|
|
201
201
|
// Dispatch to OpenClaw core using correct API (following feishu pattern)
|
|
202
202
|
log(`[BOT] 🚀 Starting dispatcher with session: ${route.sessionKey}`);
|
|
203
|
+
// Build session context for AsyncLocalStorage
|
|
204
|
+
const sessionContext = {
|
|
205
|
+
config,
|
|
206
|
+
sessionId: parsed.sessionId,
|
|
207
|
+
taskId: parsed.taskId,
|
|
208
|
+
messageId: parsed.messageId,
|
|
209
|
+
agentId: route.accountId,
|
|
210
|
+
};
|
|
203
211
|
await core.channel.reply.withReplyDispatcher({
|
|
204
212
|
dispatcher,
|
|
205
213
|
onSettled: () => {
|
|
@@ -217,12 +225,14 @@ export async function handleXYMessage(params) {
|
|
|
217
225
|
unregisterSession(route.sessionKey);
|
|
218
226
|
log(`[BOT] ✅ Cleanup completed`);
|
|
219
227
|
},
|
|
220
|
-
run: () =>
|
|
228
|
+
run: () =>
|
|
229
|
+
// 🔐 Use AsyncLocalStorage to provide session context to tools
|
|
230
|
+
runWithSessionContext(sessionContext, () => core.channel.reply.dispatchReplyFromConfig({
|
|
221
231
|
ctx: ctxPayload,
|
|
222
232
|
cfg,
|
|
223
233
|
dispatcher,
|
|
224
234
|
replyOptions,
|
|
225
|
-
}),
|
|
235
|
+
})),
|
|
226
236
|
});
|
|
227
237
|
log(`[BOT] ✅ Dispatcher completed for session: ${parsed.sessionId}`);
|
|
228
238
|
log(`xy: dispatch complete (session=${parsed.sessionId})`);
|
package/dist/src/channel.js
CHANGED
|
@@ -12,6 +12,16 @@ import { searchCalendarTool } from "./tools/search-calendar-tool.js";
|
|
|
12
12
|
import { searchPhotoGalleryTool } from "./tools/search-photo-gallery-tool.js";
|
|
13
13
|
import { uploadPhotoTool } from "./tools/upload-photo-tool.js";
|
|
14
14
|
import { xiaoyiGuiTool } from "./tools/xiaoyi-gui-tool.js";
|
|
15
|
+
import { callPhoneTool } from "./tools/call-phone-tool.js";
|
|
16
|
+
import { searchMessageTool } from "./tools/search-message-tool.js";
|
|
17
|
+
import { searchFileTool } from "./tools/search-file-tool.js";
|
|
18
|
+
import { uploadFileTool } from "./tools/upload-file-tool.js";
|
|
19
|
+
import { createAlarmTool } from "./tools/create-alarm-tool.js";
|
|
20
|
+
import { searchAlarmTool } from "./tools/search-alarm-tool.js";
|
|
21
|
+
import { modifyAlarmTool } from "./tools/modify-alarm-tool.js";
|
|
22
|
+
import { deleteAlarmTool } from "./tools/delete-alarm-tool.js";
|
|
23
|
+
import { sendMessageTool } from "./tools/send-message-tool.js";
|
|
24
|
+
import { sendFileToUserTool } from "./tools/send-file-to-user-tool.js";
|
|
15
25
|
/**
|
|
16
26
|
* Xiaoyi Channel Plugin for OpenClaw.
|
|
17
27
|
* Implements Xiaoyi A2A protocol with dual WebSocket connections.
|
|
@@ -51,7 +61,7 @@ export const xyPlugin = {
|
|
|
51
61
|
},
|
|
52
62
|
outbound: xyOutbound,
|
|
53
63
|
onboarding: xyOnboardingAdapter,
|
|
54
|
-
agentTools: [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool], // searchContactTool 已暂时禁用
|
|
64
|
+
agentTools: [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool, callPhoneTool, searchMessageTool, searchFileTool, uploadFileTool, createAlarmTool, searchAlarmTool, modifyAlarmTool, deleteAlarmTool, sendMessageTool, sendFileToUserTool], // searchContactTool 已暂时禁用
|
|
55
65
|
messaging: {
|
|
56
66
|
normalizeTarget: (raw) => {
|
|
57
67
|
const trimmed = raw.trim();
|
package/dist/src/outbound.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { resolveXYConfig } from "./config.js";
|
|
2
|
-
import { XYFileUploadService } from "./file-upload.js";
|
|
3
2
|
import { XYPushService } from "./push.js";
|
|
4
|
-
import {
|
|
3
|
+
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
5
4
|
// Special marker for default push delivery when no target is specified
|
|
6
5
|
const DEFAULT_PUSH_MARKER = "default";
|
|
7
6
|
// File extension to MIME type mapping
|
|
@@ -65,8 +64,8 @@ export const xyOutbound = {
|
|
|
65
64
|
// If the target doesn't contain "::", try to enhance it with taskId from session context
|
|
66
65
|
if (!trimmedTo.includes("::")) {
|
|
67
66
|
console.log(`[xyOutbound.resolveTarget] Target "${trimmedTo}" missing taskId, looking up session context`);
|
|
68
|
-
// Try to get the
|
|
69
|
-
const sessionContext =
|
|
67
|
+
// Try to get the current session context
|
|
68
|
+
const sessionContext = getCurrentSessionContext();
|
|
70
69
|
if (sessionContext && sessionContext.sessionId === trimmedTo) {
|
|
71
70
|
const enhancedTarget = `${trimmedTo}::${sessionContext.taskId}`;
|
|
72
71
|
console.log(`[xyOutbound.resolveTarget] Enhanced target: ${enhancedTarget}`);
|
|
@@ -130,81 +129,94 @@ export const xyOutbound = {
|
|
|
130
129
|
mediaUrl,
|
|
131
130
|
mediaLocalRoots,
|
|
132
131
|
});
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
const [sessionId, taskId] = parts;
|
|
139
|
-
// Resolve configuration
|
|
140
|
-
const config = resolveXYConfig(cfg);
|
|
141
|
-
// Create upload service
|
|
142
|
-
const uploadService = new XYFileUploadService(config.fileUploadUrl, config.apiKey, config.uid);
|
|
143
|
-
// Validate mediaUrl
|
|
144
|
-
if (!mediaUrl) {
|
|
145
|
-
throw new Error("mediaUrl is required for sendMedia");
|
|
146
|
-
}
|
|
147
|
-
// Upload file
|
|
148
|
-
const fileId = await uploadService.uploadFile(mediaUrl);
|
|
149
|
-
// Check if fileId is empty
|
|
150
|
-
if (!fileId) {
|
|
151
|
-
console.log(`[xyOutbound.sendMedia] ⚠️ File upload failed: fileId is empty, aborting sendMedia`);
|
|
152
|
-
return {
|
|
153
|
-
channel: "xiaoyi-channel",
|
|
154
|
-
messageId: "",
|
|
155
|
-
chatId: to,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
console.log(`[xyOutbound.sendMedia] File uploaded:`, {
|
|
159
|
-
fileId,
|
|
160
|
-
sessionId,
|
|
161
|
-
taskId,
|
|
162
|
-
});
|
|
163
|
-
// Get filename and mime type from mediaUrl
|
|
164
|
-
// mediaUrl may be a local file path or URL
|
|
165
|
-
const fileName = mediaUrl.split("/").pop() || "unknown";
|
|
166
|
-
const mimeType = getMimeTypeFromFilename(fileName);
|
|
167
|
-
// Build agent_response message
|
|
168
|
-
const agentResponse = {
|
|
169
|
-
msgType: "agent_response",
|
|
170
|
-
agentId: config.agentId,
|
|
171
|
-
sessionId: sessionId,
|
|
172
|
-
taskId: taskId,
|
|
173
|
-
msgDetail: JSON.stringify({
|
|
174
|
-
jsonrpc: "2.0",
|
|
175
|
-
id: taskId,
|
|
176
|
-
result: {
|
|
177
|
-
kind: "artifact-update",
|
|
178
|
-
append: true,
|
|
179
|
-
lastChunk: false,
|
|
180
|
-
final: false,
|
|
181
|
-
artifact: {
|
|
182
|
-
artifactId: taskId,
|
|
183
|
-
parts: [
|
|
184
|
-
{
|
|
185
|
-
kind: "file",
|
|
186
|
-
file: {
|
|
187
|
-
name: fileName,
|
|
188
|
-
mimeType: mimeType,
|
|
189
|
-
fileId: fileId,
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
error: { code: 0 },
|
|
196
|
-
}),
|
|
197
|
-
};
|
|
198
|
-
// Get WebSocket manager and send message
|
|
199
|
-
const { getXYWebSocketManager } = await import("./client.js");
|
|
200
|
-
const wsManager = getXYWebSocketManager(config);
|
|
201
|
-
await wsManager.sendMessage(sessionId, agentResponse);
|
|
202
|
-
console.log(`[xyOutbound.sendMedia] WebSocket message sent successfully`);
|
|
203
|
-
// Return message info
|
|
132
|
+
// All sendMedia processing logic has been disabled
|
|
133
|
+
// Use send_file_to_user tool instead for file transfers to user device
|
|
134
|
+
console.log(`[xyOutbound.sendMedia] Processing disabled, use send_file_to_user tool`);
|
|
135
|
+
// Return empty message info
|
|
204
136
|
return {
|
|
205
137
|
channel: "xiaoyi-channel",
|
|
206
|
-
messageId:
|
|
138
|
+
messageId: "",
|
|
207
139
|
chatId: to,
|
|
208
140
|
};
|
|
141
|
+
// // Parse to: "sessionId::taskId"
|
|
142
|
+
// const parts = to.split("::");
|
|
143
|
+
// if (parts.length !== 2) {
|
|
144
|
+
// throw new Error(`Invalid to format: "${to}". Expected "sessionId::taskId"`);
|
|
145
|
+
// }
|
|
146
|
+
// const [sessionId, taskId] = parts;
|
|
147
|
+
// // Resolve configuration
|
|
148
|
+
// const config = resolveXYConfig(cfg);
|
|
149
|
+
// // Create upload service
|
|
150
|
+
// const uploadService = new XYFileUploadService(
|
|
151
|
+
// config.fileUploadUrl,
|
|
152
|
+
// config.apiKey,
|
|
153
|
+
// config.uid
|
|
154
|
+
// );
|
|
155
|
+
// // Validate mediaUrl
|
|
156
|
+
// if (!mediaUrl) {
|
|
157
|
+
// throw new Error("mediaUrl is required for sendMedia");
|
|
158
|
+
// }
|
|
159
|
+
// // Upload file
|
|
160
|
+
// const fileId = await uploadService.uploadFile(mediaUrl);
|
|
161
|
+
// // Check if fileId is empty
|
|
162
|
+
// if (!fileId) {
|
|
163
|
+
// console.log(`[xyOutbound.sendMedia] ⚠️ File upload failed: fileId is empty, aborting sendMedia`);
|
|
164
|
+
// return {
|
|
165
|
+
// channel: "xiaoyi-channel",
|
|
166
|
+
// messageId: "",
|
|
167
|
+
// chatId: to,
|
|
168
|
+
// };
|
|
169
|
+
// }
|
|
170
|
+
// console.log(`[xyOutbound.sendMedia] File uploaded:`, {
|
|
171
|
+
// fileId,
|
|
172
|
+
// sessionId,
|
|
173
|
+
// taskId,
|
|
174
|
+
// });
|
|
175
|
+
// // Get filename and mime type from mediaUrl
|
|
176
|
+
// // mediaUrl may be a local file path or URL
|
|
177
|
+
// const fileName = mediaUrl.split("/").pop() || "unknown";
|
|
178
|
+
// const mimeType = getMimeTypeFromFilename(fileName);
|
|
179
|
+
// // Build agent_response message
|
|
180
|
+
// const agentResponse: OutboundWebSocketMessage = {
|
|
181
|
+
// msgType: "agent_response",
|
|
182
|
+
// agentId: config.agentId,
|
|
183
|
+
// sessionId: sessionId,
|
|
184
|
+
// taskId: taskId,
|
|
185
|
+
// msgDetail: JSON.stringify({
|
|
186
|
+
// jsonrpc: "2.0",
|
|
187
|
+
// id: taskId,
|
|
188
|
+
// result: {
|
|
189
|
+
// kind: "artifact-update",
|
|
190
|
+
// append: true,
|
|
191
|
+
// lastChunk: false,
|
|
192
|
+
// final: false,
|
|
193
|
+
// artifact: {
|
|
194
|
+
// artifactId: taskId,
|
|
195
|
+
// parts: [
|
|
196
|
+
// {
|
|
197
|
+
// kind: "file",
|
|
198
|
+
// file: {
|
|
199
|
+
// name: fileName,
|
|
200
|
+
// mimeType: mimeType,
|
|
201
|
+
// fileId: fileId,
|
|
202
|
+
// },
|
|
203
|
+
// },
|
|
204
|
+
// ],
|
|
205
|
+
// },
|
|
206
|
+
// },
|
|
207
|
+
// error: { code: 0 },
|
|
208
|
+
// }),
|
|
209
|
+
// };
|
|
210
|
+
// // Get WebSocket manager and send message
|
|
211
|
+
// const { getXYWebSocketManager } = await import("./client.js");
|
|
212
|
+
// const wsManager = getXYWebSocketManager(config);
|
|
213
|
+
// await wsManager.sendMessage(sessionId, agentResponse);
|
|
214
|
+
// console.log(`[xyOutbound.sendMedia] WebSocket message sent successfully`);
|
|
215
|
+
// // Return message info
|
|
216
|
+
// return {
|
|
217
|
+
// channel: "xiaoyi-channel",
|
|
218
|
+
// messageId: fileId,
|
|
219
|
+
// chatId: to,
|
|
220
|
+
// };
|
|
209
221
|
},
|
|
210
222
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getXYWebSocketManager } from "../client.js";
|
|
2
2
|
import { sendCommand } from "../formatter.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentSessionContext } from "./session-manager.js";
|
|
4
4
|
import { logger } from "../utils/logger.js";
|
|
5
5
|
/**
|
|
6
6
|
* XY calendar event tool - creates a calendar event on user's device.
|
|
@@ -54,7 +54,7 @@ export const calendarTool = {
|
|
|
54
54
|
logger.log(`[CALENDAR_TOOL] - dtEnd timestamp: ${dtEndMs}`);
|
|
55
55
|
// Get session context
|
|
56
56
|
logger.log(`[CALENDAR_TOOL] 🔍 Attempting to get session context...`);
|
|
57
|
-
const sessionContext =
|
|
57
|
+
const sessionContext = getCurrentSessionContext();
|
|
58
58
|
if (!sessionContext) {
|
|
59
59
|
logger.error(`[CALENDAR_TOOL] ❌ FAILED: No active session found!`);
|
|
60
60
|
logger.error(`[CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
|
|
@@ -0,0 +1,183 @@
|
|
|
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 call phone tool - makes a phone call on user's device.
|
|
7
|
+
* Requires phoneNumber parameter and optional slotId (0 for primary SIM, 1 for secondary SIM).
|
|
8
|
+
*/
|
|
9
|
+
export const callPhoneTool = {
|
|
10
|
+
name: "call_phone",
|
|
11
|
+
label: "Call Phone",
|
|
12
|
+
description: "拨打电话。需要提供要拨打的电话号码。slotId参数可选,默认为0(主卡),如果用户明确要求使用副卡则设置为1。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
|
|
13
|
+
parameters: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
phoneNumber: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "要拨打的电话号码",
|
|
19
|
+
},
|
|
20
|
+
slotId: {
|
|
21
|
+
type: "number",
|
|
22
|
+
description: "SIM卡槽ID,默认为0(主卡),设置为1表示副卡。仅当用户明确要求使用副卡时才设置为1",
|
|
23
|
+
default: 0,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: ["phoneNumber"],
|
|
27
|
+
},
|
|
28
|
+
async execute(toolCallId, params) {
|
|
29
|
+
logger.log(`[CALL_PHONE_TOOL] 🚀 Starting execution`);
|
|
30
|
+
logger.log(`[CALL_PHONE_TOOL] - toolCallId: ${toolCallId}`);
|
|
31
|
+
logger.log(`[CALL_PHONE_TOOL] - params:`, JSON.stringify(params));
|
|
32
|
+
logger.log(`[CALL_PHONE_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
33
|
+
// Validate phoneNumber parameter
|
|
34
|
+
if (!params.phoneNumber || typeof params.phoneNumber !== "string" || params.phoneNumber.trim() === "") {
|
|
35
|
+
logger.error(`[CALL_PHONE_TOOL] ❌ Missing or invalid phoneNumber parameter`);
|
|
36
|
+
throw new Error("Missing required parameter: phoneNumber must be a non-empty string");
|
|
37
|
+
}
|
|
38
|
+
// Set default slotId if not provided
|
|
39
|
+
const slotId = params.slotId !== undefined && params.slotId !== null ? params.slotId : 0;
|
|
40
|
+
// Validate slotId (must be 0 or 1)
|
|
41
|
+
if (slotId !== 0 && slotId !== 1) {
|
|
42
|
+
logger.error(`[CALL_PHONE_TOOL] ❌ Invalid slotId: ${slotId}`);
|
|
43
|
+
throw new Error("Invalid slotId: must be 0 (primary SIM) or 1 (secondary SIM)");
|
|
44
|
+
}
|
|
45
|
+
logger.log(`[CALL_PHONE_TOOL] 📞 Preparing to call phone number: ${params.phoneNumber}`);
|
|
46
|
+
logger.log(`[CALL_PHONE_TOOL] - slotId: ${slotId} (${slotId === 0 ? "主卡" : "副卡"})`);
|
|
47
|
+
// Get session context
|
|
48
|
+
logger.log(`[CALL_PHONE_TOOL] 🔍 Attempting to get session context...`);
|
|
49
|
+
const sessionContext = getCurrentSessionContext();
|
|
50
|
+
if (!sessionContext) {
|
|
51
|
+
logger.error(`[CALL_PHONE_TOOL] ❌ FAILED: No active session found!`);
|
|
52
|
+
logger.error(`[CALL_PHONE_TOOL] - toolCallId: ${toolCallId}`);
|
|
53
|
+
throw new Error("No active XY session found. Call phone tool can only be used during an active conversation.");
|
|
54
|
+
}
|
|
55
|
+
logger.log(`[CALL_PHONE_TOOL] ✅ Session context found`);
|
|
56
|
+
logger.log(`[CALL_PHONE_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
57
|
+
logger.log(`[CALL_PHONE_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
58
|
+
logger.log(`[CALL_PHONE_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
59
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
60
|
+
// Get WebSocket manager
|
|
61
|
+
logger.log(`[CALL_PHONE_TOOL] 🔌 Getting WebSocket manager...`);
|
|
62
|
+
const wsManager = getXYWebSocketManager(config);
|
|
63
|
+
logger.log(`[CALL_PHONE_TOOL] ✅ WebSocket manager obtained`);
|
|
64
|
+
// Build StartCall command
|
|
65
|
+
logger.log(`[CALL_PHONE_TOOL] 📦 Building StartCall command...`);
|
|
66
|
+
const command = {
|
|
67
|
+
header: {
|
|
68
|
+
namespace: "Common",
|
|
69
|
+
name: "Action",
|
|
70
|
+
},
|
|
71
|
+
payload: {
|
|
72
|
+
cardParam: {},
|
|
73
|
+
executeParam: {
|
|
74
|
+
executeMode: "background",
|
|
75
|
+
intentName: "StartCall",
|
|
76
|
+
bundleName: "com.huawei.hmos.aidispatchservice",
|
|
77
|
+
dimension: "",
|
|
78
|
+
needUnlock: true,
|
|
79
|
+
actionResponse: true,
|
|
80
|
+
timeOut: 5,
|
|
81
|
+
intentParam: {
|
|
82
|
+
phoneNumber: params.phoneNumber.trim(),
|
|
83
|
+
slotId: slotId,
|
|
84
|
+
},
|
|
85
|
+
permissionId: [],
|
|
86
|
+
achieveType: "INTENT",
|
|
87
|
+
},
|
|
88
|
+
responses: [
|
|
89
|
+
{
|
|
90
|
+
resultCode: "",
|
|
91
|
+
displayText: "",
|
|
92
|
+
ttsText: "",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
needUploadResult: true,
|
|
96
|
+
noHalfPage: false,
|
|
97
|
+
pageControlRelated: false,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
logger.log(`[CALL_PHONE_TOOL] 📋 Command details:`, JSON.stringify(command, null, 2));
|
|
101
|
+
// Send command and wait for response (60 second timeout)
|
|
102
|
+
logger.log(`[CALL_PHONE_TOOL] ⏳ Setting up promise to wait for call response...`);
|
|
103
|
+
logger.log(`[CALL_PHONE_TOOL] - Timeout: 60 seconds`);
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const timeout = setTimeout(() => {
|
|
106
|
+
logger.error(`[CALL_PHONE_TOOL] ⏰ Timeout: No response received within 60 seconds`);
|
|
107
|
+
wsManager.off("data-event", handler);
|
|
108
|
+
reject(new Error("拨打电话超时(60秒)"));
|
|
109
|
+
}, 60000);
|
|
110
|
+
// Listen for data events from WebSocket
|
|
111
|
+
const handler = (event) => {
|
|
112
|
+
logger.log(`[CALL_PHONE_TOOL] 📨 Received data event:`, JSON.stringify(event));
|
|
113
|
+
if (event.intentName === "StartCall") {
|
|
114
|
+
logger.log(`[CALL_PHONE_TOOL] 🎯 StartCall event received`);
|
|
115
|
+
logger.log(`[CALL_PHONE_TOOL] - status: ${event.status}`);
|
|
116
|
+
clearTimeout(timeout);
|
|
117
|
+
wsManager.off("data-event", handler);
|
|
118
|
+
if (event.status === "success" && event.outputs) {
|
|
119
|
+
logger.log(`[CALL_PHONE_TOOL] ✅ Call response received`);
|
|
120
|
+
logger.log(`[CALL_PHONE_TOOL] - outputs:`, JSON.stringify(event.outputs));
|
|
121
|
+
// Check for error code in outputs
|
|
122
|
+
const code = event.outputs.code !== undefined ? event.outputs.code : null;
|
|
123
|
+
if (code !== null && code !== 0) {
|
|
124
|
+
logger.error(`[CALL_PHONE_TOOL] ❌ Device returned error`);
|
|
125
|
+
logger.error(`[CALL_PHONE_TOOL] - code: ${code}`);
|
|
126
|
+
const errorMsg = event.outputs.errorMsg || event.outputs.errMsg || "未知错误";
|
|
127
|
+
logger.error(`[CALL_PHONE_TOOL] - errorMsg: ${errorMsg}`);
|
|
128
|
+
reject(new Error(`拨打电话失败: ${errorMsg} (错误代码: ${code})`));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Return the outputs directly
|
|
132
|
+
const result = {
|
|
133
|
+
success: true,
|
|
134
|
+
code: code,
|
|
135
|
+
phoneNumber: params.phoneNumber,
|
|
136
|
+
slotId: slotId,
|
|
137
|
+
message: "电话拨打成功",
|
|
138
|
+
};
|
|
139
|
+
logger.log(`[CALL_PHONE_TOOL] 🎉 Call initiated successfully`);
|
|
140
|
+
logger.log(`[CALL_PHONE_TOOL] - phoneNumber: ${params.phoneNumber}`);
|
|
141
|
+
logger.log(`[CALL_PHONE_TOOL] - slotId: ${slotId}`);
|
|
142
|
+
resolve({
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: JSON.stringify(result),
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
logger.error(`[CALL_PHONE_TOOL] ❌ Call failed`);
|
|
153
|
+
logger.error(`[CALL_PHONE_TOOL] - status: ${event.status}`);
|
|
154
|
+
logger.error(`[CALL_PHONE_TOOL] - outputs:`, JSON.stringify(event.outputs || {}));
|
|
155
|
+
const errorDetail = event.outputs ? JSON.stringify(event.outputs) : event.status;
|
|
156
|
+
reject(new Error(`拨打电话失败: ${errorDetail}`));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
// Register event handler
|
|
161
|
+
logger.log(`[CALL_PHONE_TOOL] 📡 Registering data-event handler on WebSocket manager`);
|
|
162
|
+
wsManager.on("data-event", handler);
|
|
163
|
+
// Send the command
|
|
164
|
+
logger.log(`[CALL_PHONE_TOOL] 📤 Sending StartCall command...`);
|
|
165
|
+
sendCommand({
|
|
166
|
+
config,
|
|
167
|
+
sessionId,
|
|
168
|
+
taskId,
|
|
169
|
+
messageId,
|
|
170
|
+
command,
|
|
171
|
+
})
|
|
172
|
+
.then(() => {
|
|
173
|
+
logger.log(`[CALL_PHONE_TOOL] ✅ Command sent successfully, waiting for response...`);
|
|
174
|
+
})
|
|
175
|
+
.catch((error) => {
|
|
176
|
+
logger.error(`[CALL_PHONE_TOOL] ❌ Failed to send command:`, error);
|
|
177
|
+
clearTimeout(timeout);
|
|
178
|
+
wsManager.off("data-event", handler);
|
|
179
|
+
reject(error);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
};
|