@ynhcj/xiaoyi-channel 0.0.9 → 0.0.10-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.
@@ -5,11 +5,17 @@ import type { XYChannelConfig } from "./types.js";
5
5
  */
6
6
  export declare class XYPushService {
7
7
  private config;
8
+ private readonly DEFAULT_PUSH_URL;
9
+ private readonly REQUEST_FROM;
8
10
  constructor(config: XYChannelConfig);
11
+ /**
12
+ * Generate a random trace ID for request tracking.
13
+ */
14
+ private generateTraceId;
9
15
  /**
10
16
  * Send a push message to a user session.
11
17
  */
12
- sendPush(content: string, title: string, sessionId?: string): Promise<void>;
18
+ sendPush(content: string, title: string, data?: Record<string, any>, sessionId?: string): Promise<void>;
13
19
  /**
14
20
  * Send a push message with file attachments.
15
21
  */
package/dist/src/push.js CHANGED
@@ -1,46 +1,135 @@
1
1
  // Push message service for scheduled tasks
2
2
  import fetch from "node-fetch";
3
- import { logger } from "./utils/logger.js";
3
+ import { randomUUID } from "crypto";
4
+ import { configManager } from "./utils/config-manager.js";
4
5
  /**
5
6
  * Service for sending push messages to users.
6
7
  * Used for outbound messages and scheduled tasks.
7
8
  */
8
9
  export class XYPushService {
9
10
  config;
11
+ DEFAULT_PUSH_URL = "https://hag.cloud.huawei.com/open-ability-agent/v1/agent-webhook";
12
+ REQUEST_FROM = "openclaw";
10
13
  constructor(config) {
11
14
  this.config = config;
12
15
  }
16
+ /**
17
+ * Generate a random trace ID for request tracking.
18
+ */
19
+ generateTraceId() {
20
+ return `trace-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
21
+ }
13
22
  /**
14
23
  * Send a push message to a user session.
15
24
  */
16
- async sendPush(content, title, sessionId) {
17
- const pushUrl = this.config.pushUrl || `${this.config.fileUploadUrl}/push`;
18
- logger.debug(`Sending push message: title="${title}"`);
25
+ async sendPush(content, title, data, sessionId) {
26
+ const pushUrl = this.config.pushUrl || this.DEFAULT_PUSH_URL;
27
+ const traceId = this.generateTraceId();
28
+ // Get dynamic pushId for the session (falls back to config pushId)
29
+ const dynamicPushId = configManager.getPushId(sessionId);
30
+ const pushId = dynamicPushId || this.config.pushId;
31
+ console.log(`[PUSH] 📤 Preparing to send push message`);
32
+ console.log(`[PUSH] - Title: "${title}"`);
33
+ console.log(`[PUSH] - Content length: ${content.length} chars`);
34
+ console.log(`[PUSH] - Session ID: ${sessionId || 'none'}`);
35
+ console.log(`[PUSH] - Trace ID: ${traceId}`);
36
+ console.log(`[PUSH] - Push URL: ${pushUrl}`);
37
+ if (dynamicPushId) {
38
+ console.log(`[PUSH] - Using dynamic pushId (from session): ${pushId.substring(0, 20)}...`);
39
+ console.log(`[PUSH] - Full dynamic pushId: ${pushId}`);
40
+ }
41
+ else {
42
+ console.log(`[PUSH] - Using config pushId (fallback): ${pushId.substring(0, 20)}...`);
43
+ console.log(`[PUSH] - Full config pushId: ${pushId}`);
44
+ }
45
+ console.log(`[PUSH] - API ID: ${this.config.apiId}`);
46
+ console.log(`[PUSH] - UID: ${this.config.uid}`);
19
47
  try {
20
- const request = {
21
- apiKey: this.config.apiKey,
22
- apiId: this.config.apiId,
23
- pushId: this.config.pushId,
24
- sessionId: sessionId || "default",
25
- title,
26
- content,
48
+ const requestBody = {
49
+ jsonrpc: "2.0",
50
+ id: randomUUID(),
51
+ result: {
52
+ id: randomUUID(),
53
+ apiId: this.config.apiId,
54
+ pushId: pushId, // Use dynamic pushId
55
+ pushText: title,
56
+ kind: "task",
57
+ artifacts: [
58
+ {
59
+ artifactId: randomUUID(),
60
+ parts: [
61
+ {
62
+ kind: "text",
63
+ text: content,
64
+ },
65
+ ],
66
+ },
67
+ ],
68
+ },
27
69
  };
70
+ console.log(`[PUSH] Full request body:`, JSON.stringify(requestBody, null, 2));
28
71
  const response = await fetch(pushUrl, {
29
72
  method: "POST",
30
73
  headers: {
31
74
  "Content-Type": "application/json",
75
+ "Accept": "application/json",
76
+ "x-hag-trace-id": traceId,
77
+ "x-uid": this.config.uid,
32
78
  "x-api-key": this.config.apiKey,
33
- "x-request-from": "openclaw",
79
+ "x-request-from": this.REQUEST_FROM,
34
80
  },
35
- body: JSON.stringify(request),
81
+ body: JSON.stringify(requestBody),
36
82
  });
83
+ // Log response status and headers
84
+ console.log(`[PUSH] 📥 Response received`);
85
+ console.log(`[PUSH] - HTTP Status: ${response.status} ${response.statusText}`);
86
+ console.log(`[PUSH] - Content-Type: ${response.headers.get('content-type')}`);
87
+ console.log(`[PUSH] - Content-Length: ${response.headers.get('content-length')}`);
37
88
  if (!response.ok) {
38
- throw new Error(`Push failed: HTTP ${response.status}`);
89
+ const errorText = await response.text();
90
+ console.log(`[PUSH] ❌ Push request failed`);
91
+ console.log(`[PUSH] - HTTP Status: ${response.status}`);
92
+ console.log(`[PUSH] - Response body: ${errorText}`);
93
+ throw new Error(`Push failed: HTTP ${response.status} - ${errorText}`);
94
+ }
95
+ // Try to parse JSON response with detailed error handling
96
+ let result;
97
+ try {
98
+ const responseText = await response.text();
99
+ console.log(`[PUSH] 📄 Response body length: ${responseText.length} chars`);
100
+ console.log(`[PUSH] 📄 Response body preview: ${responseText.substring(0, 200)}`);
101
+ if (!responseText || responseText.trim() === '') {
102
+ console.log(`[PUSH] ⚠️ Received empty response body`);
103
+ result = {};
104
+ }
105
+ else {
106
+ result = JSON.parse(responseText);
107
+ }
108
+ }
109
+ catch (parseError) {
110
+ console.log(`[PUSH] ❌ Failed to parse JSON response`);
111
+ console.log(`[PUSH] - Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
112
+ throw new Error(`Invalid JSON response from push service: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
39
113
  }
40
- logger.log(`Push message sent successfully: "${title}"`);
114
+ console.log(`[PUSH] ✅ Push message sent successfully`);
115
+ console.log(`[PUSH] - Title: "${title}"`);
116
+ console.log(`[PUSH] - Trace ID: ${traceId}`);
117
+ console.log(`[PUSH] - Used pushId: ${pushId.substring(0, 20)}...`);
118
+ console.log(`[PUSH] - Response:`, result);
41
119
  }
42
120
  catch (error) {
43
- logger.error("Failed to send push message:", error);
121
+ console.log(`[PUSH] ❌ Failed to send push message`);
122
+ console.log(`[PUSH] - Trace ID: ${traceId}`);
123
+ console.log(`[PUSH] - Target URL: ${pushUrl}`);
124
+ console.log(`[PUSH] - Push ID: ${pushId.substring(0, 20)}...`);
125
+ if (error instanceof Error) {
126
+ console.log(`[PUSH] - Error name: ${error.name}`);
127
+ console.log(`[PUSH] - Error message: ${error.message}`);
128
+ console.log(`[PUSH] - Error stack:`, error.stack);
129
+ }
130
+ else {
131
+ console.log(`[PUSH] - Error:`, error);
132
+ }
44
133
  throw error;
45
134
  }
46
135
  }
@@ -48,8 +137,10 @@ export class XYPushService {
48
137
  * Send a push message with file attachments.
49
138
  */
50
139
  async sendPushWithFiles(content, title, fileIds, sessionId) {
51
- // Build content with file references
52
- const contentWithFiles = `${content}\n\n[文件: ${fileIds.join(", ")}]`;
53
- await this.sendPush(contentWithFiles, title, sessionId);
140
+ const data = {
141
+ content,
142
+ fileIds,
143
+ };
144
+ await this.sendPush(content, title, data, sessionId);
54
145
  }
55
146
  }
@@ -1,6 +1,6 @@
1
1
  import { createReplyPrefixContext } from "openclaw/plugin-sdk";
2
2
  import { getXYRuntime } from "./runtime.js";
3
- import { sendA2AResponse, sendStatusUpdate } from "./formatter.js";
3
+ import { sendA2AResponse, sendStatusUpdate, sendReasoningTextUpdate } from "./formatter.js";
4
4
  import { resolveXYConfig } from "./config.js";
5
5
  /**
6
6
  * Create a reply dispatcher for XY channel messages.
@@ -48,7 +48,7 @@ export function createXYReplyDispatcher(params) {
48
48
  }).catch((err) => {
49
49
  error(`Failed to send status update:`, err);
50
50
  });
51
- }, 60000); // 60 seconds
51
+ }, 30000); // 30 seconds
52
52
  };
53
53
  /**
54
54
  * Stop the status update interval
@@ -87,6 +87,15 @@ export function createXYReplyDispatcher(params) {
87
87
  accumulatedText += text;
88
88
  hasSentResponse = true;
89
89
  log(`[DELIVER ACCUMULATE] Accumulated text, current length=${accumulatedText.length}`);
90
+ // Also stream text as reasoningText for real-time display
91
+ await sendReasoningTextUpdate({
92
+ config,
93
+ sessionId,
94
+ taskId,
95
+ messageId,
96
+ text,
97
+ });
98
+ log(`[DELIVER] ✅ Sent deliver text as reasoningText update`);
90
99
  }
91
100
  catch (deliverError) {
92
101
  error(`Failed to deliver message:`, deliverError);
@@ -119,6 +128,16 @@ export function createXYReplyDispatcher(params) {
119
128
  if (hasSentResponse && !finalSent) {
120
129
  log(`[ON_IDLE] Sending accumulated text, length=${accumulatedText.length}`);
121
130
  try {
131
+ // Send status update before final message
132
+ await sendStatusUpdate({
133
+ config,
134
+ sessionId,
135
+ taskId,
136
+ messageId,
137
+ text: "任务处理已完成~",
138
+ state: "completed",
139
+ });
140
+ log(`[ON_IDLE] ✅ Sent completion status update`);
122
141
  await sendA2AResponse({
123
142
  config,
124
143
  sessionId,
@@ -137,14 +156,38 @@ export function createXYReplyDispatcher(params) {
137
156
  }
138
157
  else {
139
158
  log(`[ON_IDLE] Skipping final message: hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
159
+ // Task was interrupted - send failure status and error response
160
+ try {
161
+ await sendStatusUpdate({
162
+ config,
163
+ sessionId,
164
+ taskId,
165
+ messageId,
166
+ text: "任务处理中断了~",
167
+ state: "failed",
168
+ });
169
+ log(`[ON_IDLE] ✅ Sent failure status update`);
170
+ await sendA2AResponse({
171
+ config,
172
+ sessionId,
173
+ taskId,
174
+ messageId,
175
+ text: "任务执行异常,请重试~",
176
+ append: false,
177
+ final: true,
178
+ });
179
+ finalSent = true;
180
+ log(`[ON_IDLE] ✅ Sent error response`);
181
+ }
182
+ catch (err) {
183
+ error(`[ON_IDLE] Failed to send failure status and error response:`, err);
184
+ }
140
185
  }
141
186
  // Stop status updates
142
187
  stopStatusInterval();
143
188
  },
144
189
  onCleanup: () => {
145
190
  log(`[ON_CLEANUP] Reply cleanup for session ${sessionId}, hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
146
- // Stop status updates
147
- stopStatusInterval();
148
191
  },
149
192
  });
150
193
  return {
@@ -152,6 +195,106 @@ export function createXYReplyDispatcher(params) {
152
195
  replyOptions: {
153
196
  ...replyOptions,
154
197
  onModelSelected: prefixContext.onModelSelected,
198
+ // 🔧 Tool execution start callback
199
+ onToolStart: async ({ name, phase }) => {
200
+ log(`[TOOL START] 🔧 Tool execution started/updated: name=${name}, phase=${phase}, session=${sessionId}, taskId=${taskId}`);
201
+ if (phase === "start") {
202
+ const toolName = name || "unknown";
203
+ try {
204
+ await sendStatusUpdate({
205
+ config,
206
+ sessionId,
207
+ taskId,
208
+ messageId,
209
+ text: `正在使用工具: ${toolName}...`,
210
+ state: "working",
211
+ });
212
+ log(`[TOOL START] ✅ Sent status update for tool start: ${toolName}`);
213
+ }
214
+ catch (err) {
215
+ error(`[TOOL START] ❌ Failed to send tool start status:`, err);
216
+ }
217
+ }
218
+ },
219
+ // 🔧 Tool execution result callback
220
+ onToolResult: async (payload) => {
221
+ const text = payload.text ?? "";
222
+ const hasMedia = Boolean(payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0);
223
+ log(`[TOOL RESULT] 🔧 Tool execution result received: session=${sessionId}, taskId=${taskId}`);
224
+ log(`[TOOL RESULT] - text.length=${text.length}`);
225
+ log(`[TOOL RESULT] - hasMedia=${hasMedia}`);
226
+ log(`[TOOL RESULT] - isError=${payload.isError}`);
227
+ if (text.length > 0) {
228
+ log(`[TOOL RESULT] - text preview: "${text.slice(0, 200)}"`);
229
+ }
230
+ try {
231
+ if (text.length > 0 || hasMedia) {
232
+ const resultText = text.length > 0 ? text : "工具执行完成";
233
+ await sendStatusUpdate({
234
+ config,
235
+ sessionId,
236
+ taskId,
237
+ messageId,
238
+ text: resultText,
239
+ state: "working",
240
+ });
241
+ log(`[TOOL RESULT] ✅ Sent tool result as status update`);
242
+ }
243
+ }
244
+ catch (err) {
245
+ error(`[TOOL RESULT] ❌ Failed to send tool result status:`, err);
246
+ }
247
+ },
248
+ // 🧠 Reasoning/thinking process streaming callback
249
+ onReasoningStream: async (payload) => {
250
+ const text = payload.text ?? "";
251
+ log(`[REASONING STREAM] 🧠 Reasoning/thinking chunk received: session=${sessionId}, taskId=${taskId}`);
252
+ log(`[REASONING STREAM] - text.length=${text.length}`);
253
+ if (text.length > 0) {
254
+ log(`[REASONING STREAM] - text preview: "${text.slice(0, 200)}"`);
255
+ }
256
+ // try {
257
+ // if (text.length > 0) {
258
+ // await sendReasoningTextUpdate({
259
+ // config,
260
+ // sessionId,
261
+ // taskId,
262
+ // messageId,
263
+ // text,
264
+ // });
265
+ // log(`[REASONING STREAM] ✅ Sent reasoning chunk as reasoningText update`);
266
+ // }
267
+ // } catch (err) {
268
+ // error(`[REASONING STREAM] ❌ Failed to send reasoning chunk reasoningText:`, err);
269
+ // }
270
+ },
271
+ // 📝 Partial reply streaming callback (real-time preview)
272
+ onPartialReply: async (payload) => {
273
+ const text = payload.text ?? "";
274
+ const hasMedia = Boolean(payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0);
275
+ log(`[PARTIAL REPLY] 📝 Partial reply chunk received: session=${sessionId}, taskId=${taskId}`);
276
+ log(`[PARTIAL REPLY] - text.length=${text.length}`);
277
+ log(`[PARTIAL REPLY] - hasMedia=${hasMedia}`);
278
+ if (text.length > 0) {
279
+ log(`[PARTIAL REPLY] - text preview: "${text.slice(0, 200)}"`);
280
+ }
281
+ try {
282
+ if (text.length > 0) {
283
+ await sendReasoningTextUpdate({
284
+ config,
285
+ sessionId,
286
+ taskId,
287
+ messageId,
288
+ text,
289
+ append: false,
290
+ });
291
+ log(`[PARTIAL REPLY] ✅ Sent partial reply as reasoningText update (append=false)`);
292
+ }
293
+ }
294
+ catch (err) {
295
+ error(`[PARTIAL REPLY] ❌ Failed to send partial reply reasoningText:`, err);
296
+ }
297
+ },
155
298
  },
156
299
  markDispatchIdle,
157
300
  startStatusInterval, // Expose this to be called immediately
@@ -0,0 +1,6 @@
1
+ /**
2
+ * XY calendar event tool - creates a calendar event on user's device.
3
+ * Requires title, dtStart (start time), and dtEnd (end time) parameters.
4
+ * Time format must be: yyyy-mm-dd hh:mm:ss
5
+ */
6
+ export declare const calendarTool: any;
@@ -0,0 +1,167 @@
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 calendar event tool - creates a calendar event on user's device.
7
+ * Requires title, dtStart (start time), and dtEnd (end time) parameters.
8
+ * Time format must be: yyyy-mm-dd hh:mm:ss
9
+ */
10
+ export const calendarTool = {
11
+ name: "create_calendar_event",
12
+ label: "Create Calendar Event",
13
+ description: "在用户设备上创建日程。需要提供日程标题、开始时间和结束时间。时间格式必须为:yyyy-mm-dd hh:mm:ss(例如:2024-01-15 14:30:00)。注意:该工具执行时间较长(最多60秒),请勿重复调用,超时或失败时最多重试一次。",
14
+ parameters: {
15
+ type: "object",
16
+ properties: {
17
+ title: {
18
+ type: "string",
19
+ description: "日程标题/名称",
20
+ },
21
+ dtStart: {
22
+ type: "string",
23
+ description: "日程开始时间,格式必须为:yyyy-mm-dd hh:mm:ss(例如:2024-01-15 14:30:00)",
24
+ },
25
+ dtEnd: {
26
+ type: "string",
27
+ description: "日程结束时间,格式必须为:yyyy-mm-dd hh:mm:ss(例如:2024-01-15 17:30:00)",
28
+ },
29
+ },
30
+ required: ["title", "dtStart", "dtEnd"],
31
+ },
32
+ async execute(toolCallId, params) {
33
+ logger.log(`[CALENDAR_TOOL] 🚀 Starting execution`);
34
+ logger.log(`[CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
35
+ logger.log(`[CALENDAR_TOOL] - params:`, JSON.stringify(params));
36
+ logger.log(`[CALENDAR_TOOL] - timestamp: ${new Date().toISOString()}`);
37
+ // Validate parameters
38
+ if (!params.title || !params.dtStart || !params.dtEnd) {
39
+ logger.error(`[CALENDAR_TOOL] ❌ Missing required parameters`);
40
+ throw new Error("Missing required parameters: title, dtStart, and dtEnd are required");
41
+ }
42
+ // Convert time strings to millisecond timestamps
43
+ logger.log(`[CALENDAR_TOOL] 🕒 Converting time strings to timestamps...`);
44
+ logger.log(`[CALENDAR_TOOL] - dtStart input: ${params.dtStart}`);
45
+ logger.log(`[CALENDAR_TOOL] - dtEnd input: ${params.dtEnd}`);
46
+ const dtStartMs = new Date(params.dtStart).getTime();
47
+ const dtEndMs = new Date(params.dtEnd).getTime();
48
+ if (isNaN(dtStartMs) || isNaN(dtEndMs)) {
49
+ logger.error(`[CALENDAR_TOOL] ❌ Invalid time format`);
50
+ throw new Error("Invalid time format. Required format: yyyy-mm-dd hh:mm:ss (e.g., 2024-01-15 14:30:00)");
51
+ }
52
+ logger.log(`[CALENDAR_TOOL] ✅ Time conversion successful`);
53
+ logger.log(`[CALENDAR_TOOL] - dtStart timestamp: ${dtStartMs}`);
54
+ logger.log(`[CALENDAR_TOOL] - dtEnd timestamp: ${dtEndMs}`);
55
+ // Get session context
56
+ logger.log(`[CALENDAR_TOOL] 🔍 Attempting to get session context...`);
57
+ const sessionContext = getLatestSessionContext();
58
+ if (!sessionContext) {
59
+ logger.error(`[CALENDAR_TOOL] ❌ FAILED: No active session found!`);
60
+ logger.error(`[CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
61
+ throw new Error("No active XY session found. Calendar tool can only be used during an active conversation.");
62
+ }
63
+ logger.log(`[CALENDAR_TOOL] ✅ Session context found`);
64
+ logger.log(`[CALENDAR_TOOL] - sessionId: ${sessionContext.sessionId}`);
65
+ logger.log(`[CALENDAR_TOOL] - taskId: ${sessionContext.taskId}`);
66
+ logger.log(`[CALENDAR_TOOL] - messageId: ${sessionContext.messageId}`);
67
+ const { config, sessionId, taskId, messageId } = sessionContext;
68
+ // Get WebSocket manager
69
+ logger.log(`[CALENDAR_TOOL] 🔌 Getting WebSocket manager...`);
70
+ const wsManager = getXYWebSocketManager(config);
71
+ logger.log(`[CALENDAR_TOOL] ✅ WebSocket manager obtained`);
72
+ // Build CreateCalendarEvent command
73
+ logger.log(`[CALENDAR_TOOL] 📦 Building CreateCalendarEvent command...`);
74
+ const command = {
75
+ header: {
76
+ namespace: "Common",
77
+ name: "ActionAndResult",
78
+ },
79
+ payload: {
80
+ cardParam: {},
81
+ executeParam: {
82
+ executeMode: "background",
83
+ intentName: "CreateCalendarEvent",
84
+ bundleName: "com.huawei.hmos.calendardata",
85
+ dimension: "",
86
+ needUnlock: true,
87
+ actionResponse: true,
88
+ timeOut: 5,
89
+ intentParam: {
90
+ title: params.title,
91
+ dtStart: dtStartMs,
92
+ dtEnd: dtEndMs,
93
+ },
94
+ achieveType: "INTENT",
95
+ },
96
+ responses: [
97
+ {
98
+ resultCode: "",
99
+ displayText: "",
100
+ ttsText: "",
101
+ },
102
+ ],
103
+ needUploadResult: true,
104
+ noHalfPage: false,
105
+ pageControlRelated: false,
106
+ },
107
+ };
108
+ // Send command and wait for response (60 second timeout)
109
+ logger.log(`[CALENDAR_TOOL] ⏳ Setting up promise to wait for calendar event response...`);
110
+ logger.log(`[CALENDAR_TOOL] - Timeout: 60 seconds`);
111
+ return new Promise((resolve, reject) => {
112
+ const timeout = setTimeout(() => {
113
+ logger.error(`[CALENDAR_TOOL] ⏰ Timeout: No response received within 60 seconds`);
114
+ wsManager.off("data-event", handler);
115
+ reject(new Error("创建日程超时(60秒)"));
116
+ }, 60000);
117
+ // Listen for data events from WebSocket
118
+ const handler = (event) => {
119
+ logger.log(`[CALENDAR_TOOL] 📨 Received data event:`, JSON.stringify(event));
120
+ if (event.intentName === "CreateCalendarEvent") {
121
+ logger.log(`[CALENDAR_TOOL] 🎯 CreateCalendarEvent event received`);
122
+ logger.log(`[CALENDAR_TOOL] - status: ${event.status}`);
123
+ clearTimeout(timeout);
124
+ wsManager.off("data-event", handler);
125
+ if (event.status === "success" && event.outputs) {
126
+ logger.log(`[CALENDAR_TOOL] ✅ Calendar event created successfully`);
127
+ logger.log(`[CALENDAR_TOOL] - outputs:`, JSON.stringify(event.outputs));
128
+ resolve({
129
+ content: [
130
+ {
131
+ type: "text",
132
+ text: JSON.stringify(event.outputs),
133
+ },
134
+ ],
135
+ });
136
+ }
137
+ else {
138
+ logger.error(`[CALENDAR_TOOL] ❌ Calendar event creation failed`);
139
+ logger.error(`[CALENDAR_TOOL] - status: ${event.status}`);
140
+ reject(new Error(`创建日程失败: ${event.status}`));
141
+ }
142
+ }
143
+ };
144
+ // Register event handler
145
+ logger.log(`[CALENDAR_TOOL] 📡 Registering data-event handler on WebSocket manager`);
146
+ wsManager.on("data-event", handler);
147
+ // Send the command
148
+ logger.log(`[CALENDAR_TOOL] 📤 Sending CreateCalendarEvent command...`);
149
+ sendCommand({
150
+ config,
151
+ sessionId,
152
+ taskId,
153
+ messageId,
154
+ command,
155
+ })
156
+ .then(() => {
157
+ logger.log(`[CALENDAR_TOOL] ✅ Command sent successfully, waiting for response...`);
158
+ })
159
+ .catch((error) => {
160
+ logger.error(`[CALENDAR_TOOL] ❌ Failed to send command:`, error);
161
+ clearTimeout(timeout);
162
+ wsManager.off("data-event", handler);
163
+ reject(error);
164
+ });
165
+ });
166
+ },
167
+ };
@@ -9,7 +9,7 @@ import { logger } from "../utils/logger.js";
9
9
  export const locationTool = {
10
10
  name: "get_user_location",
11
11
  label: "Get User Location",
12
- description: "获取用户当前位置(经纬度坐标,WGS84坐标系)。需要用户设备授权位置访问权限。",
12
+ description: "获取用户当前位置(经纬度坐标,WGS84坐标系)。需要用户设备授权位置访问权限。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
13
13
  parameters: {
14
14
  type: "object",
15
15
  properties: {},
@@ -47,27 +47,37 @@ export const locationTool = {
47
47
  name: "Action",
48
48
  },
49
49
  payload: {
50
+ cardParam: {},
50
51
  executeParam: {
51
52
  achieveType: "INTENT",
52
53
  actionResponse: true,
53
54
  bundleName: "com.huawei.hmos.aidispatchservice",
55
+ dimension: "",
56
+ executeMode: "background",
54
57
  intentName: "GetCurrentLocation",
55
58
  intentParam: {},
56
59
  needUnlock: true,
60
+ permissionId: [],
57
61
  timeOut: 5,
58
62
  },
59
63
  needUploadResult: true,
64
+ pageControlRelated: false,
65
+ responses: [{
66
+ displayText: "",
67
+ resultCode: "",
68
+ ttsText: "",
69
+ }],
60
70
  },
61
71
  };
62
- // Send command and wait for response (5 second timeout)
72
+ // Send command and wait for response (60 second timeout)
63
73
  logger.log(`[LOCATION_TOOL] ⏳ Setting up promise to wait for location response...`);
64
- logger.log(`[LOCATION_TOOL] - Timeout: 5 seconds`);
74
+ logger.log(`[LOCATION_TOOL] - Timeout: 60 seconds`);
65
75
  return new Promise((resolve, reject) => {
66
76
  const timeout = setTimeout(() => {
67
- logger.error(`[LOCATION_TOOL] ⏰ Timeout: No response received within 5 seconds`);
77
+ logger.error(`[LOCATION_TOOL] ⏰ Timeout: No response received within 60 seconds`);
68
78
  wsManager.off("data-event", handler);
69
- reject(new Error("获取位置超时(5秒)"));
70
- }, 5000);
79
+ reject(new Error("获取位置超时(60秒)"));
80
+ }, 60000);
71
81
  // Listen for data events from WebSocket
72
82
  const handler = (event) => {
73
83
  logger.log(`[LOCATION_TOOL] 📨 Received data event:`, JSON.stringify(event));
@@ -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;