@ynhcj/xiaoyi-channel 0.0.22-beta → 0.0.22-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.
Files changed (78) hide show
  1. package/dist/index.js +42 -0
  2. package/dist/src/bot.js +123 -41
  3. package/dist/src/channel.js +17 -5
  4. package/dist/src/client.js +11 -24
  5. package/dist/src/config.js +2 -2
  6. package/dist/src/cspl/call-api.d.ts +3 -0
  7. package/dist/src/cspl/call-api.js +79 -0
  8. package/dist/src/cspl/config.d.ts +19 -0
  9. package/dist/src/cspl/config.js +50 -0
  10. package/dist/src/cspl/constants.d.ts +43 -0
  11. package/dist/src/cspl/constants.js +22 -0
  12. package/dist/src/cspl/utils.d.ts +10 -0
  13. package/dist/src/cspl/utils.js +57 -0
  14. package/dist/src/file-upload.d.ts +5 -0
  15. package/dist/src/file-upload.js +92 -0
  16. package/dist/src/formatter.d.ts +14 -0
  17. package/dist/src/formatter.js +46 -2
  18. package/dist/src/monitor.js +47 -5
  19. package/dist/src/outbound.js +52 -6
  20. package/dist/src/parser.d.ts +7 -0
  21. package/dist/src/parser.js +22 -0
  22. package/dist/src/push.d.ts +8 -1
  23. package/dist/src/push.js +30 -22
  24. package/dist/src/reply-dispatcher.d.ts +1 -0
  25. package/dist/src/reply-dispatcher.js +116 -101
  26. package/dist/src/steer-injector.d.ts +16 -0
  27. package/dist/src/steer-injector.js +74 -0
  28. package/dist/src/task-manager.d.ts +55 -0
  29. package/dist/src/task-manager.js +136 -0
  30. package/dist/src/tools/calendar-tool.js +5 -3
  31. package/dist/src/tools/call-phone-tool.d.ts +5 -0
  32. package/dist/src/tools/call-phone-tool.js +183 -0
  33. package/dist/src/tools/create-alarm-tool.d.ts +7 -0
  34. package/dist/src/tools/create-alarm-tool.js +420 -0
  35. package/dist/src/tools/delete-alarm-tool.d.ts +11 -0
  36. package/dist/src/tools/delete-alarm-tool.js +216 -0
  37. package/dist/src/tools/image-reading-tool.d.ts +5 -0
  38. package/dist/src/tools/image-reading-tool.js +375 -0
  39. package/dist/src/tools/location-tool.js +8 -11
  40. package/dist/src/tools/modify-alarm-tool.d.ts +9 -0
  41. package/dist/src/tools/modify-alarm-tool.js +439 -0
  42. package/dist/src/tools/modify-note-tool.js +4 -9
  43. package/dist/src/tools/note-tool.js +32 -21
  44. package/dist/src/tools/search-alarm-tool.d.ts +8 -0
  45. package/dist/src/tools/search-alarm-tool.js +343 -0
  46. package/dist/src/tools/search-calendar-tool.js +9 -46
  47. package/dist/src/tools/search-contact-tool.js +4 -27
  48. package/dist/src/tools/search-file-tool.d.ts +5 -0
  49. package/dist/src/tools/search-file-tool.js +161 -0
  50. package/dist/src/tools/search-message-tool.d.ts +5 -0
  51. package/dist/src/tools/search-message-tool.js +149 -0
  52. package/dist/src/tools/search-note-tool.js +29 -22
  53. package/dist/src/tools/search-photo-gallery-tool.js +51 -34
  54. package/dist/src/tools/send-file-to-user-tool.d.ts +5 -0
  55. package/dist/src/tools/send-file-to-user-tool.js +318 -0
  56. package/dist/src/tools/send-message-tool.d.ts +5 -0
  57. package/dist/src/tools/send-message-tool.js +176 -0
  58. package/dist/src/tools/session-manager.d.ts +15 -0
  59. package/dist/src/tools/session-manager.js +99 -18
  60. package/dist/src/tools/upload-file-tool.d.ts +13 -0
  61. package/dist/src/tools/upload-file-tool.js +265 -0
  62. package/dist/src/tools/upload-photo-tool.js +14 -4
  63. package/dist/src/tools/view-push-result-tool.d.ts +5 -0
  64. package/dist/src/tools/view-push-result-tool.js +118 -0
  65. package/dist/src/tools/xiaoyi-collection-tool.d.ts +5 -0
  66. package/dist/src/tools/xiaoyi-collection-tool.js +190 -0
  67. package/dist/src/tools/xiaoyi-gui-tool.js +8 -8
  68. package/dist/src/trigger-handler.d.ts +22 -0
  69. package/dist/src/trigger-handler.js +59 -0
  70. package/dist/src/types.d.ts +1 -8
  71. package/dist/src/types.js +4 -0
  72. package/dist/src/utils/pushdata-manager.d.ts +28 -0
  73. package/dist/src/utils/pushdata-manager.js +171 -0
  74. package/dist/src/utils/pushid-manager.d.ts +12 -0
  75. package/dist/src/utils/pushid-manager.js +105 -0
  76. package/dist/src/websocket.d.ts +25 -31
  77. package/dist/src/websocket.js +218 -270
  78. package/package.json +1 -1
@@ -0,0 +1,136 @@
1
+ // TaskId Manager - 管理session级别的活跃taskId
2
+ // 支持动态切换taskId,用于steer模式下的消息插队
3
+ import { logger } from "./utils/logger.js";
4
+ /**
5
+ * Session到活跃TaskId的映射
6
+ * Key: sessionId (注意:这里用sessionId,不是sessionKey)
7
+ * Value: TaskIdBinding
8
+ */
9
+ const activeTaskIds = new Map();
10
+ /**
11
+ * 注册或更新session的活跃taskId
12
+ * 返回是否是更新(用于判断是否是第二条消息)
13
+ */
14
+ export function registerTaskId(sessionId, taskId, messageId, options) {
15
+ logger.log(`[TASK_MANAGER] 📝 Registering/Updating taskId for session: ${sessionId}`);
16
+ logger.log(`[TASK_MANAGER] - New taskId: ${taskId}`);
17
+ logger.log(`[TASK_MANAGER] - New messageId: ${messageId}`);
18
+ logger.log(`[TASK_MANAGER] - incrementRef: ${options?.incrementRef ?? false}`);
19
+ const existing = activeTaskIds.get(sessionId);
20
+ if (existing) {
21
+ logger.log(`[TASK_MANAGER] - Previous taskId: ${existing.currentTaskId}`);
22
+ logger.log(`[TASK_MANAGER] - Previous refCount: ${existing.refCount}`);
23
+ logger.log(`[TASK_MANAGER] - 🔄 Switching taskId (steer mode detected)`);
24
+ // 更新taskId,但保持引用计数
25
+ existing.currentTaskId = taskId;
26
+ existing.currentMessageId = messageId;
27
+ existing.updatedAt = Date.now();
28
+ if (options?.incrementRef) {
29
+ existing.refCount++;
30
+ logger.log(`[TASK_MANAGER] - Incremented refCount: ${existing.refCount}`);
31
+ }
32
+ logger.log(`[TASK_MANAGER] - ✅ TaskId updated, refCount=${existing.refCount}`);
33
+ return { isUpdate: true, refCount: existing.refCount };
34
+ }
35
+ else {
36
+ // 新注册
37
+ const binding = {
38
+ sessionId,
39
+ currentTaskId: taskId,
40
+ currentMessageId: messageId,
41
+ refCount: 1,
42
+ updatedAt: Date.now(),
43
+ locked: false,
44
+ };
45
+ activeTaskIds.set(sessionId, binding);
46
+ logger.log(`[TASK_MANAGER] - ✅ TaskId registered (new), refCount=1`);
47
+ return { isUpdate: false, refCount: 1 };
48
+ }
49
+ }
50
+ /**
51
+ * 增加引用计数(消息开始处理时调用)
52
+ */
53
+ export function incrementTaskIdRef(sessionId) {
54
+ const binding = activeTaskIds.get(sessionId);
55
+ if (binding) {
56
+ binding.refCount++;
57
+ logger.log(`[TASK_MANAGER] ➕ Incremented refCount for ${sessionId}: ${binding.refCount}`);
58
+ }
59
+ }
60
+ /**
61
+ * 减少引用计数,当refCount=0时才真正清理
62
+ */
63
+ export function decrementTaskIdRef(sessionId) {
64
+ const binding = activeTaskIds.get(sessionId);
65
+ if (!binding) {
66
+ logger.log(`[TASK_MANAGER] ⚠️ No binding found for ${sessionId}`);
67
+ return;
68
+ }
69
+ binding.refCount--;
70
+ logger.log(`[TASK_MANAGER] ➖ Decremented refCount for ${sessionId}: ${binding.refCount}`);
71
+ if (binding.refCount <= 0 && !binding.locked) {
72
+ logger.log(`[TASK_MANAGER] 🗑️ RefCount=0 and unlocked, clearing taskId`);
73
+ activeTaskIds.delete(sessionId);
74
+ }
75
+ else {
76
+ logger.log(`[TASK_MANAGER] - Keeping binding (refCount=${binding.refCount}, locked=${binding.locked})`);
77
+ }
78
+ }
79
+ /**
80
+ * 锁定taskId,防止被清理(第一个消息使用)
81
+ */
82
+ export function lockTaskId(sessionId) {
83
+ const binding = activeTaskIds.get(sessionId);
84
+ if (binding) {
85
+ binding.locked = true;
86
+ logger.log(`[TASK_MANAGER] 🔒 Locked taskId for ${sessionId}`);
87
+ }
88
+ }
89
+ /**
90
+ * 解锁taskId(第一个消息完成时使用)
91
+ */
92
+ export function unlockTaskId(sessionId) {
93
+ const binding = activeTaskIds.get(sessionId);
94
+ if (binding) {
95
+ binding.locked = false;
96
+ logger.log(`[TASK_MANAGER] 🔓 Unlocked taskId for ${sessionId}`);
97
+ // 解锁后,如果refCount=0,立即清理
98
+ if (binding.refCount <= 0) {
99
+ logger.log(`[TASK_MANAGER] 🗑️ Unlocked and refCount=0, clearing taskId`);
100
+ activeTaskIds.delete(sessionId);
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * 获取session的当前活跃taskId
106
+ */
107
+ export function getCurrentTaskId(sessionId) {
108
+ const binding = activeTaskIds.get(sessionId);
109
+ return binding?.currentTaskId ?? null;
110
+ }
111
+ /**
112
+ * 获取session的当前活跃messageId
113
+ */
114
+ export function getCurrentMessageId(sessionId) {
115
+ const binding = activeTaskIds.get(sessionId);
116
+ return binding?.currentMessageId ?? null;
117
+ }
118
+ /**
119
+ * 检查session是否有活跃的taskId
120
+ */
121
+ export function hasActiveTask(sessionId) {
122
+ return activeTaskIds.has(sessionId);
123
+ }
124
+ /**
125
+ * 获取完整的binding信息(用于调试)
126
+ */
127
+ export function getTaskIdBinding(sessionId) {
128
+ return activeTaskIds.get(sessionId) ?? null;
129
+ }
130
+ /**
131
+ * 强制清理(错误恢复用)
132
+ */
133
+ export function forceCleanTaskId(sessionId) {
134
+ logger.log(`[TASK_MANAGER] ⚠️ Force clearing taskId for ${sessionId}`);
135
+ activeTaskIds.delete(sessionId);
136
+ }
@@ -1,6 +1,6 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { sendCommand } from "../formatter.js";
3
- import { getLatestSessionContext } from "./session-manager.js";
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.
@@ -10,7 +10,9 @@ import { logger } from "../utils/logger.js";
10
10
  export const calendarTool = {
11
11
  name: "create_calendar_event",
12
12
  label: "Create Calendar Event",
13
- description: "在用户设备上创建日程。需要提供日程标题、开始时间和结束时间。时间格式必须为:yyyy-mm-dd hh:mm:ss(例如:2024-01-15 14:30:00)。注意:该工具执行时间较长(最多60秒),请勿重复调用,超时或失败时最多重试一次。",
13
+ description: `在用户设备上创建日程。需要提供日程标题、开始时间和结束时间。时间格式必须为:yyyy-mm-dd hh:mm:ss(例如:2024-01-15 14:30:00)。注意:该工具执行时间较长(最多60秒),请勿重复调用,超时或失败时最多重试一次。
14
+ 注意事项:使用该工具之前需获取当前真实时间
15
+ `,
14
16
  parameters: {
15
17
  type: "object",
16
18
  properties: {
@@ -54,7 +56,7 @@ export const calendarTool = {
54
56
  logger.log(`[CALENDAR_TOOL] - dtEnd timestamp: ${dtEndMs}`);
55
57
  // Get session context
56
58
  logger.log(`[CALENDAR_TOOL] 🔍 Attempting to get session context...`);
57
- const sessionContext = getLatestSessionContext();
59
+ const sessionContext = getCurrentSessionContext();
58
60
  if (!sessionContext) {
59
61
  logger.error(`[CALENDAR_TOOL] ❌ FAILED: No active session found!`);
60
62
  logger.error(`[CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
@@ -0,0 +1,5 @@
1
+ /**
2
+ * XY call phone tool - makes a phone call on user's device.
3
+ * Requires phoneNumber parameter and optional slotId (0 for primary SIM, 1 for secondary SIM).
4
+ */
5
+ export declare const callPhoneTool: any;
@@ -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
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * XY create alarm tool - creates an alarm on user's device.
3
+ * Requires alarmTime parameter. Other parameters are optional with sensible defaults.
4
+ *
5
+ * Time format: YYYYMMDD hhmmss (e.g., 20240315 143000)
6
+ */
7
+ export declare const createAlarmTool: any;