@ynhcj/xiaoyi-channel 0.0.9 → 0.0.11-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.
@@ -0,0 +1,163 @@
1
+ import { getXYWebSocketManager } from "../client.js";
2
+ import { sendCommand } from "../formatter.js";
3
+ import { getLatestSessionContext } from "./session-manager.js";
4
+ import { logger } from "../utils/logger.js";
5
+ /**
6
+ * XY modify note tool - appends content to an existing note on user's device.
7
+ * Requires entityId from search_notes tool as prerequisite.
8
+ *
9
+ * Prerequisites:
10
+ * 1. Call search_notes tool first to get the entityId of target note
11
+ * 2. Use the entityId to append content to that note
12
+ */
13
+ export const modifyNoteTool = {
14
+ name: "modify_note",
15
+ label: "Modify Note",
16
+ description: "在指定备忘录中追加新内容。使用前必须先调用 search_notes 工具获取备忘录的 entityId。参数说明:entityId 是备忘录的唯一标识符(从 search_notes 工具获取),text 是要追加的文本内容。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
17
+ parameters: {
18
+ type: "object",
19
+ properties: {
20
+ entityId: {
21
+ type: "string",
22
+ description: "备忘录的唯一标识符,必须先通过 search_notes 工具获取",
23
+ },
24
+ text: {
25
+ type: "string",
26
+ description: "要追加到备忘录的文本内容",
27
+ },
28
+ },
29
+ required: ["entityId", "text"],
30
+ },
31
+ async execute(toolCallId, params) {
32
+ logger.log(`[MODIFY_NOTE_TOOL] 🚀 Starting execution`);
33
+ logger.log(`[MODIFY_NOTE_TOOL] - toolCallId: ${toolCallId}`);
34
+ logger.log(`[MODIFY_NOTE_TOOL] - params:`, JSON.stringify(params));
35
+ logger.log(`[MODIFY_NOTE_TOOL] - timestamp: ${new Date().toISOString()}`);
36
+ // Validate parameters
37
+ if (!params.entityId || !params.text) {
38
+ logger.error(`[MODIFY_NOTE_TOOL] ❌ Missing required parameters`);
39
+ throw new Error("Missing required parameters: entityId and text are required");
40
+ }
41
+ // Get session context
42
+ logger.log(`[MODIFY_NOTE_TOOL] 🔍 Attempting to get session context...`);
43
+ const sessionContext = getLatestSessionContext();
44
+ if (!sessionContext) {
45
+ logger.error(`[MODIFY_NOTE_TOOL] ❌ FAILED: No active session found!`);
46
+ logger.error(`[MODIFY_NOTE_TOOL] - toolCallId: ${toolCallId}`);
47
+ throw new Error("No active XY session found. Modify note tool can only be used during an active conversation.");
48
+ }
49
+ logger.log(`[MODIFY_NOTE_TOOL] ✅ Session context found`);
50
+ logger.log(`[MODIFY_NOTE_TOOL] - sessionId: ${sessionContext.sessionId}`);
51
+ logger.log(`[MODIFY_NOTE_TOOL] - taskId: ${sessionContext.taskId}`);
52
+ logger.log(`[MODIFY_NOTE_TOOL] - messageId: ${sessionContext.messageId}`);
53
+ const { config, sessionId, taskId, messageId } = sessionContext;
54
+ // Get WebSocket manager
55
+ logger.log(`[MODIFY_NOTE_TOOL] 🔌 Getting WebSocket manager...`);
56
+ const wsManager = getXYWebSocketManager(config);
57
+ logger.log(`[MODIFY_NOTE_TOOL] ✅ WebSocket manager obtained`);
58
+ // Build ModifyNote command
59
+ logger.log(`[MODIFY_NOTE_TOOL] 📦 Building ModifyNote command...`);
60
+ const command = {
61
+ header: {
62
+ namespace: "Common",
63
+ name: "Action",
64
+ },
65
+ payload: {
66
+ cardParam: {},
67
+ executeParam: {
68
+ executeMode: "background",
69
+ intentName: "ModifyNote",
70
+ bundleName: "com.huawei.hmos.notepad",
71
+ needUnlock: true,
72
+ actionResponse: true,
73
+ appType: "OHOS_APP",
74
+ timeOut: 5,
75
+ intentParam: {
76
+ contentType: "1", // 1 = append mode (追加模式)
77
+ text: params.text,
78
+ entityId: params.entityId,
79
+ },
80
+ permissionId: [],
81
+ achieveType: "INTENT",
82
+ },
83
+ responses: [
84
+ {
85
+ resultCode: "",
86
+ displayText: "",
87
+ ttsText: "",
88
+ },
89
+ ],
90
+ needUploadResult: true,
91
+ noHalfPage: false,
92
+ pageControlRelated: false,
93
+ },
94
+ };
95
+ logger.log(`[MODIFY_NOTE_TOOL] - entityId: ${params.entityId}`);
96
+ logger.log(`[MODIFY_NOTE_TOOL] - contentType: 1 (append mode)`);
97
+ logger.log(`[MODIFY_NOTE_TOOL] - text length: ${params.text.length} characters`);
98
+ // Send command and wait for response (60 second timeout)
99
+ logger.log(`[MODIFY_NOTE_TOOL] ⏳ Setting up promise to wait for note modification response...`);
100
+ logger.log(`[MODIFY_NOTE_TOOL] - Timeout: 60 seconds`);
101
+ return new Promise((resolve, reject) => {
102
+ const timeout = setTimeout(() => {
103
+ logger.error(`[MODIFY_NOTE_TOOL] ⏰ Timeout: No response received within 60 seconds`);
104
+ wsManager.off("data-event", handler);
105
+ reject(new Error("修改备忘录超时(60秒)"));
106
+ }, 60000);
107
+ // Listen for data events from WebSocket
108
+ const handler = (event) => {
109
+ logger.log(`[MODIFY_NOTE_TOOL] 📨 Received data event:`, JSON.stringify(event));
110
+ if (event.intentName === "ModifyNote") {
111
+ logger.log(`[MODIFY_NOTE_TOOL] 🎯 ModifyNote event received`);
112
+ logger.log(`[MODIFY_NOTE_TOOL] - status: ${event.status}`);
113
+ clearTimeout(timeout);
114
+ wsManager.off("data-event", handler);
115
+ if (event.status === "success" && event.outputs) {
116
+ logger.log(`[MODIFY_NOTE_TOOL] ✅ Note modified successfully`);
117
+ logger.log(`[MODIFY_NOTE_TOOL] - outputs:`, JSON.stringify(event.outputs));
118
+ // Return the result directly as requested
119
+ const result = event.outputs.result;
120
+ logger.log(`[MODIFY_NOTE_TOOL] 📝 Note updated:`);
121
+ logger.log(`[MODIFY_NOTE_TOOL] - entityId: ${result?.entityId}`);
122
+ logger.log(`[MODIFY_NOTE_TOOL] - title: ${result?.title}`);
123
+ logger.log(`[MODIFY_NOTE_TOOL] - modifiedDate: ${result?.modifiedDate}`);
124
+ resolve({
125
+ content: [
126
+ {
127
+ type: "text",
128
+ text: JSON.stringify(result),
129
+ },
130
+ ],
131
+ });
132
+ }
133
+ else {
134
+ logger.error(`[MODIFY_NOTE_TOOL] ❌ Note modification failed`);
135
+ logger.error(`[MODIFY_NOTE_TOOL] - status: ${event.status}`);
136
+ reject(new Error(`修改备忘录失败: ${event.status}`));
137
+ }
138
+ }
139
+ };
140
+ // Register event handler
141
+ logger.log(`[MODIFY_NOTE_TOOL] 📡 Registering data-event handler on WebSocket manager`);
142
+ wsManager.on("data-event", handler);
143
+ // Send the command
144
+ logger.log(`[MODIFY_NOTE_TOOL] 📤 Sending ModifyNote command...`);
145
+ sendCommand({
146
+ config,
147
+ sessionId,
148
+ taskId,
149
+ messageId,
150
+ command,
151
+ })
152
+ .then(() => {
153
+ logger.log(`[MODIFY_NOTE_TOOL] ✅ Command sent successfully, waiting for response...`);
154
+ })
155
+ .catch((error) => {
156
+ logger.error(`[MODIFY_NOTE_TOOL] ❌ Failed to send command:`, error);
157
+ clearTimeout(timeout);
158
+ wsManager.off("data-event", handler);
159
+ reject(error);
160
+ });
161
+ });
162
+ },
163
+ };
@@ -9,7 +9,7 @@ import { logger } from "../utils/logger.js";
9
9
  export const noteTool = {
10
10
  name: "create_note",
11
11
  label: "Create Note",
12
- description: "在用户设备上创建备忘录。需要提供备忘录标题和内容。",
12
+ description: "在用户设备上创建备忘录。需要提供备忘录标题和内容。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
13
13
  parameters: {
14
14
  type: "object",
15
15
  properties: {
@@ -72,12 +72,12 @@ export const noteTool = {
72
72
  pageControlRelated: false,
73
73
  },
74
74
  };
75
- // Send command and wait for response (5 second timeout)
75
+ // Send command and wait for response (60 second timeout)
76
76
  return new Promise((resolve, reject) => {
77
77
  const timeout = setTimeout(() => {
78
78
  wsManager.off("data-event", handler);
79
- reject(new Error("创建备忘录超时(5秒)"));
80
- }, 5000);
79
+ reject(new Error("创建备忘录超时(60秒)"));
80
+ }, 60000);
81
81
  // Listen for data events from WebSocket
82
82
  const handler = (event) => {
83
83
  logger.debug("Received data event:", event);
@@ -0,0 +1,12 @@
1
+ /**
2
+ * XY search calendar event tool - searches calendar events on user's device.
3
+ * Returns matching events based on time range and optional title filter.
4
+ *
5
+ * Time range guidelines:
6
+ * - For a specific day: use 00:00:00 to 23:59:59 of that day
7
+ * - For morning: 06:00:00 to 12:00:00
8
+ * - For afternoon: 12:00:00 to 18:00:00
9
+ * - For evening: 18:00:00 to 24:00:00
10
+ * - For a specific time: use ±1 hour range (e.g., for 3PM, use 14:00:00 to 16:00:00)
11
+ */
12
+ export declare const searchCalendarTool: any;
@@ -0,0 +1,220 @@
1
+ import { getXYWebSocketManager } from "../client.js";
2
+ import { sendCommand } from "../formatter.js";
3
+ import { getLatestSessionContext } from "./session-manager.js";
4
+ import { logger } from "../utils/logger.js";
5
+ /**
6
+ * XY search calendar event tool - searches calendar events on user's device.
7
+ * Returns matching events based on time range and optional title filter.
8
+ *
9
+ * Time range guidelines:
10
+ * - For a specific day: use 00:00:00 to 23:59:59 of that day
11
+ * - For morning: 06:00:00 to 12:00:00
12
+ * - For afternoon: 12:00:00 to 18:00:00
13
+ * - For evening: 18:00:00 to 24:00:00
14
+ * - For a specific time: use ±1 hour range (e.g., for 3PM, use 14:00:00 to 16:00:00)
15
+ */
16
+ export const searchCalendarTool = {
17
+ name: "search_calendar_event",
18
+ label: "Search Calendar Event",
19
+ description: `检索用户日历中的日程安排。根据时间范围和可选的日程标题进行检索。时间格式必须为:YYYYMMDD hhmmss(例如:20240115 143000)。
20
+
21
+ 时间范围说明:
22
+ - 查询某一天的日程:使用该天的 00:00:00 到 23:59:59(例如:20240115 000000 到 20240115 235959)
23
+ - 查询上午的日程:使用 06:00:00 到 12:00:00
24
+ - 查询下午的日程:使用 12:00:00 到 18:00:00
25
+ - 查询晚上的日程:使用 18:00:00 到 23:59:59
26
+ - 查询某个时刻附近的日程:使用该时刻前后1小时的区间(例如:查询3点左右的日程,使用 14:00:00 到 16:00:00)
27
+
28
+ 注意:该工具执行时间较长(最多60秒),请勿重复调用,超时或失败时最多重试一次。`,
29
+ parameters: {
30
+ type: "object",
31
+ properties: {
32
+ startTime: {
33
+ type: "string",
34
+ description: "日程起始时间,格式必须为:YYYYMMDD hhmmss(例如:20240115 143000 表示 2024年1月15日 14:30:00)",
35
+ },
36
+ endTime: {
37
+ type: "string",
38
+ description: "日程结束时间,格式必须为:YYYYMMDD hhmmss(例如:20240115 173000 表示 2024年1月15日 17:30:00)",
39
+ },
40
+ title: {
41
+ type: "string",
42
+ description: "日程标题/类型(可选),用于过滤特定类型的日程",
43
+ },
44
+ },
45
+ required: ["startTime", "endTime"],
46
+ },
47
+ async execute(toolCallId, params) {
48
+ logger.log(`[SEARCH_CALENDAR_TOOL] 🚀 Starting execution`);
49
+ logger.log(`[SEARCH_CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
50
+ logger.log(`[SEARCH_CALENDAR_TOOL] - params:`, JSON.stringify(params));
51
+ logger.log(`[SEARCH_CALENDAR_TOOL] - timestamp: ${new Date().toISOString()}`);
52
+ // Validate parameters
53
+ if (!params.startTime || !params.endTime) {
54
+ logger.error(`[SEARCH_CALENDAR_TOOL] ❌ Missing required parameters`);
55
+ throw new Error("Missing required parameters: startTime and endTime are required");
56
+ }
57
+ // Convert time strings to millisecond timestamps
58
+ logger.log(`[SEARCH_CALENDAR_TOOL] 🕒 Converting time strings to timestamps...`);
59
+ logger.log(`[SEARCH_CALENDAR_TOOL] - startTime input: ${params.startTime}`);
60
+ logger.log(`[SEARCH_CALENDAR_TOOL] - endTime input: ${params.endTime}`);
61
+ // Parse YYYYMMDD hhmmss format
62
+ const parseTimeString = (timeStr) => {
63
+ // Remove any extra spaces and split
64
+ const cleaned = timeStr.trim().replace(/\s+/g, ' ');
65
+ const parts = cleaned.split(' ');
66
+ if (parts.length !== 2) {
67
+ throw new Error(`Invalid time format: ${timeStr}. Expected format: YYYYMMDD hhmmss`);
68
+ }
69
+ const datePart = parts[0]; // YYYYMMDD
70
+ const timePart = parts[1]; // hhmmss
71
+ if (datePart.length !== 8 || timePart.length !== 6) {
72
+ throw new Error(`Invalid time format: ${timeStr}. Expected format: YYYYMMDD hhmmss`);
73
+ }
74
+ const year = parseInt(datePart.substring(0, 4), 10);
75
+ const month = parseInt(datePart.substring(4, 6), 10) - 1; // Month is 0-indexed
76
+ const day = parseInt(datePart.substring(6, 8), 10);
77
+ const hours = parseInt(timePart.substring(0, 2), 10);
78
+ const minutes = parseInt(timePart.substring(2, 4), 10);
79
+ const seconds = parseInt(timePart.substring(4, 6), 10);
80
+ const date = new Date(year, month, day, hours, minutes, seconds);
81
+ return date.getTime();
82
+ };
83
+ let startTimeMs;
84
+ let endTimeMs;
85
+ try {
86
+ startTimeMs = parseTimeString(params.startTime);
87
+ endTimeMs = parseTimeString(params.endTime);
88
+ }
89
+ catch (error) {
90
+ logger.error(`[SEARCH_CALENDAR_TOOL] ❌ Time parsing error:`, error);
91
+ throw new Error(`Invalid time format. Required format: YYYYMMDD hhmmss (e.g., 20240115 143000). Error: ${error instanceof Error ? error.message : String(error)}`);
92
+ }
93
+ if (isNaN(startTimeMs) || isNaN(endTimeMs)) {
94
+ logger.error(`[SEARCH_CALENDAR_TOOL] ❌ Invalid time format`);
95
+ throw new Error("Invalid time format. Required format: YYYYMMDD hhmmss (e.g., 20240115 143000)");
96
+ }
97
+ logger.log(`[SEARCH_CALENDAR_TOOL] ✅ Time conversion successful`);
98
+ logger.log(`[SEARCH_CALENDAR_TOOL] - startTime timestamp: ${startTimeMs}`);
99
+ logger.log(`[SEARCH_CALENDAR_TOOL] - endTime timestamp: ${endTimeMs}`);
100
+ // Get session context
101
+ logger.log(`[SEARCH_CALENDAR_TOOL] 🔍 Attempting to get session context...`);
102
+ const sessionContext = getLatestSessionContext();
103
+ if (!sessionContext) {
104
+ logger.error(`[SEARCH_CALENDAR_TOOL] ❌ FAILED: No active session found!`);
105
+ logger.error(`[SEARCH_CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
106
+ throw new Error("No active XY session found. Search calendar tool can only be used during an active conversation.");
107
+ }
108
+ logger.log(`[SEARCH_CALENDAR_TOOL] ✅ Session context found`);
109
+ logger.log(`[SEARCH_CALENDAR_TOOL] - sessionId: ${sessionContext.sessionId}`);
110
+ logger.log(`[SEARCH_CALENDAR_TOOL] - taskId: ${sessionContext.taskId}`);
111
+ logger.log(`[SEARCH_CALENDAR_TOOL] - messageId: ${sessionContext.messageId}`);
112
+ const { config, sessionId, taskId, messageId } = sessionContext;
113
+ // Get WebSocket manager
114
+ logger.log(`[SEARCH_CALENDAR_TOOL] 🔌 Getting WebSocket manager...`);
115
+ const wsManager = getXYWebSocketManager(config);
116
+ logger.log(`[SEARCH_CALENDAR_TOOL] ✅ WebSocket manager obtained`);
117
+ // Build SearchCalendarEvent command
118
+ logger.log(`[SEARCH_CALENDAR_TOOL] 📦 Building SearchCalendarEvent command...`);
119
+ // Build intentParam with timeInterval and optional title
120
+ const intentParam = {
121
+ timeInterval: [startTimeMs, endTimeMs],
122
+ };
123
+ if (params.title) {
124
+ intentParam.title = params.title;
125
+ logger.log(`[SEARCH_CALENDAR_TOOL] - Including title filter: ${params.title}`);
126
+ }
127
+ const command = {
128
+ header: {
129
+ namespace: "Common",
130
+ name: "Action",
131
+ },
132
+ payload: {
133
+ cardParam: {},
134
+ executeParam: {
135
+ executeMode: "background",
136
+ intentName: "SearchCalendarEvent",
137
+ bundleName: "com.huawei.hmos.calendardata",
138
+ dimension: "",
139
+ needUnlock: true,
140
+ actionResponse: true,
141
+ appType: "OHOS_APP",
142
+ timeOut: 5,
143
+ intentParam,
144
+ permissionId: [],
145
+ achieveType: "INTENT",
146
+ },
147
+ responses: [
148
+ {
149
+ resultCode: "",
150
+ displayText: "",
151
+ ttsText: "",
152
+ },
153
+ ],
154
+ needUploadResult: true,
155
+ noHalfPage: false,
156
+ pageControlRelated: false,
157
+ },
158
+ };
159
+ // Send command and wait for response (60 second timeout)
160
+ logger.log(`[SEARCH_CALENDAR_TOOL] ⏳ Setting up promise to wait for calendar search response...`);
161
+ logger.log(`[SEARCH_CALENDAR_TOOL] - Timeout: 60 seconds`);
162
+ return new Promise((resolve, reject) => {
163
+ const timeout = setTimeout(() => {
164
+ logger.error(`[SEARCH_CALENDAR_TOOL] ⏰ Timeout: No response received within 60 seconds`);
165
+ wsManager.off("data-event", handler);
166
+ reject(new Error("检索日程超时(60秒)"));
167
+ }, 60000);
168
+ // Listen for data events from WebSocket
169
+ const handler = (event) => {
170
+ logger.log(`[SEARCH_CALENDAR_TOOL] 📨 Received data event:`, JSON.stringify(event));
171
+ if (event.intentName === "SearchCalendarEvent") {
172
+ logger.log(`[SEARCH_CALENDAR_TOOL] 🎯 SearchCalendarEvent event received`);
173
+ logger.log(`[SEARCH_CALENDAR_TOOL] - status: ${event.status}`);
174
+ clearTimeout(timeout);
175
+ wsManager.off("data-event", handler);
176
+ if (event.status === "success" && event.outputs) {
177
+ logger.log(`[SEARCH_CALENDAR_TOOL] ✅ Calendar events retrieved successfully`);
178
+ logger.log(`[SEARCH_CALENDAR_TOOL] - outputs:`, JSON.stringify(event.outputs));
179
+ // Return the result directly as requested
180
+ const result = event.outputs.result;
181
+ resolve({
182
+ content: [
183
+ {
184
+ type: "text",
185
+ text: JSON.stringify(result),
186
+ },
187
+ ],
188
+ });
189
+ }
190
+ else {
191
+ logger.error(`[SEARCH_CALENDAR_TOOL] ❌ Calendar event search failed`);
192
+ logger.error(`[SEARCH_CALENDAR_TOOL] - status: ${event.status}`);
193
+ reject(new Error(`检索日程失败: ${event.status}`));
194
+ }
195
+ }
196
+ };
197
+ // Register event handler
198
+ logger.log(`[SEARCH_CALENDAR_TOOL] 📡 Registering data-event handler on WebSocket manager`);
199
+ wsManager.on("data-event", handler);
200
+ // Send the command
201
+ logger.log(`[SEARCH_CALENDAR_TOOL] 📤 Sending SearchCalendarEvent command...`);
202
+ sendCommand({
203
+ config,
204
+ sessionId,
205
+ taskId,
206
+ messageId,
207
+ command,
208
+ })
209
+ .then(() => {
210
+ logger.log(`[SEARCH_CALENDAR_TOOL] ✅ Command sent successfully, waiting for response...`);
211
+ })
212
+ .catch((error) => {
213
+ logger.error(`[SEARCH_CALENDAR_TOOL] ❌ Failed to send command:`, error);
214
+ clearTimeout(timeout);
215
+ wsManager.off("data-event", handler);
216
+ reject(error);
217
+ });
218
+ });
219
+ },
220
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * XY search contact tool - searches contacts on user's device.
3
+ * Returns matching contact information based on name.
4
+ */
5
+ export declare const searchContactTool: any;
@@ -0,0 +1,147 @@
1
+ import { getXYWebSocketManager } from "../client.js";
2
+ import { sendCommand } from "../formatter.js";
3
+ import { getLatestSessionContext } from "./session-manager.js";
4
+ import { logger } from "../utils/logger.js";
5
+ /**
6
+ * XY search contact tool - searches contacts on user's device.
7
+ * Returns matching contact information based on name.
8
+ */
9
+ export const searchContactTool = {
10
+ name: "search_contact",
11
+ label: "Search Contact",
12
+ description: "搜索用户设备上的联系人信息。根据姓名在通讯录中检索联系人详细信息(包括姓名、电话号码、邮箱、组织、职位等)。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
13
+ parameters: {
14
+ type: "object",
15
+ properties: {
16
+ name: {
17
+ type: "string",
18
+ description: "联系人姓名,用于在通讯录中检索联系人信息",
19
+ },
20
+ },
21
+ required: ["name"],
22
+ },
23
+ async execute(toolCallId, params) {
24
+ logger.log(`[SEARCH_CONTACT_TOOL] 🚀 Starting execution`);
25
+ logger.log(`[SEARCH_CONTACT_TOOL] - toolCallId: ${toolCallId}`);
26
+ logger.log(`[SEARCH_CONTACT_TOOL] - params:`, JSON.stringify(params));
27
+ logger.log(`[SEARCH_CONTACT_TOOL] - timestamp: ${new Date().toISOString()}`);
28
+ // Validate parameters
29
+ if (!params.name) {
30
+ logger.error(`[SEARCH_CONTACT_TOOL] ❌ Missing required parameter: name`);
31
+ throw new Error("Missing required parameter: name is required");
32
+ }
33
+ // Get session context
34
+ logger.log(`[SEARCH_CONTACT_TOOL] 🔍 Attempting to get session context...`);
35
+ const sessionContext = getLatestSessionContext();
36
+ if (!sessionContext) {
37
+ logger.error(`[SEARCH_CONTACT_TOOL] ❌ FAILED: No active session found!`);
38
+ logger.error(`[SEARCH_CONTACT_TOOL] - toolCallId: ${toolCallId}`);
39
+ throw new Error("No active XY session found. Search contact tool can only be used during an active conversation.");
40
+ }
41
+ logger.log(`[SEARCH_CONTACT_TOOL] ✅ Session context found`);
42
+ logger.log(`[SEARCH_CONTACT_TOOL] - sessionId: ${sessionContext.sessionId}`);
43
+ logger.log(`[SEARCH_CONTACT_TOOL] - taskId: ${sessionContext.taskId}`);
44
+ logger.log(`[SEARCH_CONTACT_TOOL] - messageId: ${sessionContext.messageId}`);
45
+ const { config, sessionId, taskId, messageId } = sessionContext;
46
+ // Get WebSocket manager
47
+ logger.log(`[SEARCH_CONTACT_TOOL] 🔌 Getting WebSocket manager...`);
48
+ const wsManager = getXYWebSocketManager(config);
49
+ logger.log(`[SEARCH_CONTACT_TOOL] ✅ WebSocket manager obtained`);
50
+ // Build SearchContactLocal command
51
+ logger.log(`[SEARCH_CONTACT_TOOL] 📦 Building SearchContactLocal command...`);
52
+ const command = {
53
+ header: {
54
+ namespace: "Common",
55
+ name: "Action",
56
+ },
57
+ payload: {
58
+ cardParam: {},
59
+ executeParam: {
60
+ executeMode: "background",
61
+ intentName: "SearchContactLocal",
62
+ bundleName: "com.huawei.hmos.aidispatchservice",
63
+ needUnlock: true,
64
+ actionResponse: true,
65
+ appType: "OHOS_APP",
66
+ timeOut: 5,
67
+ intentParam: {
68
+ name: params.name,
69
+ },
70
+ permissionId: [],
71
+ achieveType: "INTENT",
72
+ },
73
+ responses: [
74
+ {
75
+ resultCode: "",
76
+ displayText: "",
77
+ ttsText: "",
78
+ },
79
+ ],
80
+ needUploadResult: true,
81
+ noHalfPage: false,
82
+ pageControlRelated: false,
83
+ },
84
+ };
85
+ // Send command and wait for response (60 second timeout)
86
+ logger.log(`[SEARCH_CONTACT_TOOL] ⏳ Setting up promise to wait for contact search response...`);
87
+ logger.log(`[SEARCH_CONTACT_TOOL] - Timeout: 60 seconds`);
88
+ return new Promise((resolve, reject) => {
89
+ const timeout = setTimeout(() => {
90
+ logger.error(`[SEARCH_CONTACT_TOOL] ⏰ Timeout: No response received within 60 seconds`);
91
+ wsManager.off("data-event", handler);
92
+ reject(new Error("搜索联系人超时(60秒)"));
93
+ }, 60000);
94
+ // Listen for data events from WebSocket
95
+ const handler = (event) => {
96
+ logger.log(`[SEARCH_CONTACT_TOOL] 📨 Received data event:`, JSON.stringify(event));
97
+ if (event.intentName === "SearchContactLocal") {
98
+ logger.log(`[SEARCH_CONTACT_TOOL] 🎯 SearchContactLocal event received`);
99
+ logger.log(`[SEARCH_CONTACT_TOOL] - status: ${event.status}`);
100
+ clearTimeout(timeout);
101
+ wsManager.off("data-event", handler);
102
+ if (event.status === "success" && event.outputs) {
103
+ logger.log(`[SEARCH_CONTACT_TOOL] ✅ Contact search completed successfully`);
104
+ logger.log(`[SEARCH_CONTACT_TOOL] - outputs:`, JSON.stringify(event.outputs));
105
+ // Return the result directly as requested
106
+ const result = event.outputs.result;
107
+ logger.log(`[SEARCH_CONTACT_TOOL] 📊 Contacts found: ${result?.items?.length || 0} results for name "${params.name}"`);
108
+ resolve({
109
+ content: [
110
+ {
111
+ type: "text",
112
+ text: JSON.stringify(result),
113
+ },
114
+ ],
115
+ });
116
+ }
117
+ else {
118
+ logger.error(`[SEARCH_CONTACT_TOOL] ❌ Contact search failed`);
119
+ logger.error(`[SEARCH_CONTACT_TOOL] - status: ${event.status}`);
120
+ reject(new Error(`搜索联系人失败: ${event.status}`));
121
+ }
122
+ }
123
+ };
124
+ // Register event handler
125
+ logger.log(`[SEARCH_CONTACT_TOOL] 📡 Registering data-event handler on WebSocket manager`);
126
+ wsManager.on("data-event", handler);
127
+ // Send the command
128
+ logger.log(`[SEARCH_CONTACT_TOOL] 📤 Sending SearchContactLocal command...`);
129
+ sendCommand({
130
+ config,
131
+ sessionId,
132
+ taskId,
133
+ messageId,
134
+ command,
135
+ })
136
+ .then(() => {
137
+ logger.log(`[SEARCH_CONTACT_TOOL] ✅ Command sent successfully, waiting for response...`);
138
+ })
139
+ .catch((error) => {
140
+ logger.error(`[SEARCH_CONTACT_TOOL] ❌ Failed to send command:`, error);
141
+ clearTimeout(timeout);
142
+ wsManager.off("data-event", handler);
143
+ reject(error);
144
+ });
145
+ });
146
+ },
147
+ };
@@ -9,7 +9,7 @@ import { logger } from "../utils/logger.js";
9
9
  export const searchNoteTool = {
10
10
  name: "search_notes",
11
11
  label: "Search Notes",
12
- description: "搜索用户设备上的备忘录内容。根据关键词在备忘录的标题、内容和附件名称中进行检索。",
12
+ description: "搜索用户设备上的备忘录内容。根据关键词在备忘录的标题、内容和附件名称中进行检索。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
13
13
  parameters: {
14
14
  type: "object",
15
15
  properties: {
@@ -67,12 +67,12 @@ export const searchNoteTool = {
67
67
  pageControlRelated: false,
68
68
  },
69
69
  };
70
- // Send command and wait for response (5 second timeout)
70
+ // Send command and wait for response (60 second timeout)
71
71
  return new Promise((resolve, reject) => {
72
72
  const timeout = setTimeout(() => {
73
73
  wsManager.off("data-event", handler);
74
- reject(new Error("搜索备忘录超时(5秒)"));
75
- }, 5000);
74
+ reject(new Error("搜索备忘录超时(60秒)"));
75
+ }, 60000);
76
76
  // Listen for data events from WebSocket
77
77
  const handler = (event) => {
78
78
  logger.debug("Received data event:", event);
@@ -1,4 +1,5 @@
1
1
  import { logger } from "../utils/logger.js";
2
+ import { configManager } from "../utils/config-manager.js";
2
3
  // Map of sessionKey -> SessionContext
3
4
  const activeSessions = new Map();
4
5
  /**
@@ -24,7 +25,13 @@ export function unregisterSession(sessionKey) {
24
25
  logger.log(`[SESSION_MANAGER] 🗑️ Unregistering session: ${sessionKey}`);
25
26
  logger.log(`[SESSION_MANAGER] - Active sessions before: ${activeSessions.size}`);
26
27
  logger.log(`[SESSION_MANAGER] - Session existed: ${activeSessions.has(sessionKey)}`);
28
+ // Get session context before deleting to clear associated pushId
29
+ const context = activeSessions.get(sessionKey);
27
30
  const existed = activeSessions.delete(sessionKey);
31
+ // Clear cached pushId for this session
32
+ if (context) {
33
+ configManager.clearSession(context.sessionId);
34
+ }
28
35
  logger.log(`[SESSION_MANAGER] - Deleted: ${existed}`);
29
36
  logger.log(`[SESSION_MANAGER] - Active sessions after: ${activeSessions.size}`);
30
37
  logger.log(`[SESSION_MANAGER] - Remaining session keys: [${Array.from(activeSessions.keys()).join(", ")}]`);
@@ -75,7 +75,11 @@ export interface A2AArtifact {
75
75
  artifactId: string;
76
76
  parts: A2AArtifactPart[];
77
77
  }
78
- export type A2AArtifactPart = A2ATextPart | A2ADataPart | A2ACommandPart;
78
+ export interface A2AReasoningTextPart {
79
+ kind: "reasoningText";
80
+ reasoningText: string;
81
+ }
82
+ export type A2AArtifactPart = A2ATextPart | A2ADataPart | A2ACommandPart | A2AReasoningTextPart;
79
83
  export interface A2ACommandPart {
80
84
  kind: "command";
81
85
  command: A2ACommand;
@@ -155,14 +159,6 @@ export interface FileUploadCompleteRequest {
155
159
  export interface FileUploadCompleteResponse {
156
160
  fileId: string;
157
161
  }
158
- export interface PushMessageRequest {
159
- apiKey: string;
160
- apiId: string;
161
- pushId: string;
162
- sessionId: string;
163
- title: string;
164
- content: string;
165
- }
166
162
  export type ServerIdentifier = "server1" | "server2";
167
163
  export interface SessionBinding {
168
164
  sessionId: string;