@ynhcj/xiaoyi-channel 0.0.32-beta → 0.0.32-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 (75) hide show
  1. package/dist/index.d.ts +0 -2
  2. package/dist/index.js +42 -2
  3. package/dist/src/bot.js +60 -56
  4. package/dist/src/channel.js +24 -8
  5. package/dist/src/client.js +11 -33
  6. package/dist/src/config.js +2 -2
  7. package/dist/src/cspl/call-api.d.ts +3 -0
  8. package/dist/src/cspl/call-api.js +86 -0
  9. package/dist/src/cspl/config.d.ts +19 -0
  10. package/dist/src/cspl/config.js +50 -0
  11. package/dist/src/cspl/constants.d.ts +43 -0
  12. package/dist/src/cspl/constants.js +22 -0
  13. package/dist/src/cspl/utils.d.ts +10 -0
  14. package/dist/src/cspl/utils.js +57 -0
  15. package/dist/src/file-upload.d.ts +5 -0
  16. package/dist/src/file-upload.js +88 -6
  17. package/dist/src/formatter.d.ts +14 -0
  18. package/dist/src/formatter.js +49 -28
  19. package/dist/src/heartbeat.js +0 -4
  20. package/dist/src/monitor.js +16 -10
  21. package/dist/src/onboarding.d.ts +3 -4
  22. package/dist/src/onboarding.js +2 -2
  23. package/dist/src/outbound.d.ts +2 -1
  24. package/dist/src/outbound.js +119 -103
  25. package/dist/src/parser.d.ts +7 -0
  26. package/dist/src/parser.js +22 -0
  27. package/dist/src/push.d.ts +8 -1
  28. package/dist/src/push.js +27 -40
  29. package/dist/src/reply-dispatcher.js +39 -7
  30. package/dist/src/steer-injector.d.ts +16 -0
  31. package/dist/src/steer-injector.js +74 -0
  32. package/dist/src/thread-bindings.d.ts +54 -0
  33. package/dist/src/thread-bindings.js +214 -0
  34. package/dist/src/tools/calendar-tool.js +5 -38
  35. package/dist/src/tools/call-phone-tool.js +1 -42
  36. package/dist/src/tools/create-alarm-tool.js +8 -103
  37. package/dist/src/tools/delete-alarm-tool.js +5 -69
  38. package/dist/src/tools/image-reading-tool.d.ts +5 -0
  39. package/dist/src/tools/image-reading-tool.js +328 -0
  40. package/dist/src/tools/location-tool.js +6 -40
  41. package/dist/src/tools/modify-alarm-tool.js +8 -117
  42. package/dist/src/tools/modify-note-tool.js +3 -41
  43. package/dist/src/tools/note-tool.js +4 -16
  44. package/dist/src/tools/search-alarm-tool.js +14 -118
  45. package/dist/src/tools/search-calendar-tool.js +8 -82
  46. package/dist/src/tools/search-contact-tool.js +2 -55
  47. package/dist/src/tools/search-file-tool.js +4 -61
  48. package/dist/src/tools/search-message-tool.js +2 -59
  49. package/dist/src/tools/search-note-tool.js +4 -22
  50. package/dist/src/tools/search-photo-gallery-tool.js +38 -59
  51. package/dist/src/tools/send-file-to-user-tool.js +1 -39
  52. package/dist/src/tools/send-message-tool.js +5 -56
  53. package/dist/src/tools/session-manager.js +0 -45
  54. package/dist/src/tools/upload-file-tool.js +0 -49
  55. package/dist/src/tools/upload-photo-tool.js +0 -42
  56. package/dist/src/tools/view-push-result-tool.d.ts +5 -0
  57. package/dist/src/tools/view-push-result-tool.js +107 -0
  58. package/dist/src/tools/xiaoyi-collection-tool.d.ts +5 -0
  59. package/dist/src/tools/xiaoyi-collection-tool.js +112 -0
  60. package/dist/src/tools/xiaoyi-gui-tool.js +0 -34
  61. package/dist/src/trigger-handler.d.ts +22 -0
  62. package/dist/src/trigger-handler.js +59 -0
  63. package/dist/src/types.d.ts +1 -8
  64. package/dist/src/types.js +4 -0
  65. package/dist/src/utils/pushdata-manager.d.ts +28 -0
  66. package/dist/src/utils/pushdata-manager.js +171 -0
  67. package/dist/src/utils/pushid-manager.d.ts +12 -0
  68. package/dist/src/utils/pushid-manager.js +105 -0
  69. package/dist/src/websocket.d.ts +25 -31
  70. package/dist/src/websocket.js +239 -271
  71. package/package.json +2 -2
  72. package/dist/src/tools/search-photo-tool.d.ts +0 -9
  73. package/dist/src/tools/search-photo-tool.js +0 -270
  74. package/dist/src/utils/session.d.ts +0 -34
  75. package/dist/src/utils/session.js +0 -50
@@ -0,0 +1,54 @@
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
2
+ import { type BindingTargetKind } from "openclaw/plugin-sdk/conversation-runtime";
3
+ type XYBindingTargetKind = "subagent" | "session";
4
+ /**
5
+ * Xiaoyi thread binding record.
6
+ * Simplified from feishu - uses sessionId as conversationId, no parentConversationId.
7
+ */
8
+ type XYThreadBindingRecord = {
9
+ accountId: string;
10
+ sessionId: string;
11
+ targetKind: XYBindingTargetKind;
12
+ targetSessionKey: string;
13
+ agentId?: string;
14
+ boundAt: number;
15
+ lastActivityAt: number;
16
+ };
17
+ /**
18
+ * Thread binding manager for Xiaoyi channel.
19
+ * Manages session bindings for single-account mode.
20
+ */
21
+ type XYThreadBindingManager = {
22
+ accountId: string;
23
+ getBySessionId: (sessionId: string) => XYThreadBindingRecord | undefined;
24
+ listBySessionKey: (targetSessionKey: string) => XYThreadBindingRecord[];
25
+ bindSession: (params: {
26
+ sessionId: string;
27
+ targetKind: BindingTargetKind;
28
+ targetSessionKey: string;
29
+ metadata?: Record<string, unknown>;
30
+ }) => XYThreadBindingRecord | null;
31
+ touchSession: (sessionId: string, at?: number) => XYThreadBindingRecord | null;
32
+ unbindSession: (sessionId: string) => XYThreadBindingRecord | null;
33
+ unbindBySessionKey: (targetSessionKey: string) => XYThreadBindingRecord[];
34
+ stop: () => void;
35
+ };
36
+ /**
37
+ * Creates a thread binding manager for Xiaoyi channel.
38
+ * Based on feishu implementation but simplified for single-account mode.
39
+ */
40
+ export declare function createXYThreadBindingManager(params: {
41
+ accountId?: string;
42
+ cfg: OpenClawConfig;
43
+ }): XYThreadBindingManager;
44
+ /**
45
+ * Gets the thread binding manager for a given account ID.
46
+ */
47
+ export declare function getXYThreadBindingManager(accountId?: string): XYThreadBindingManager | null;
48
+ /**
49
+ * Testing utilities for thread bindings.
50
+ */
51
+ export declare const __testing: {
52
+ resetXYThreadBindingsForTests(): void;
53
+ };
54
+ export {};
@@ -0,0 +1,214 @@
1
+ import { resolveThreadBindingIdleTimeoutMsForChannel, resolveThreadBindingMaxAgeMsForChannel, registerSessionBindingAdapter, resolveThreadBindingConversationIdFromBindingId, unregisterSessionBindingAdapter, } from "openclaw/plugin-sdk/conversation-runtime";
2
+ import { normalizeAccountId, resolveAgentIdFromSessionKey } from "openclaw/plugin-sdk/routing";
3
+ import { resolveGlobalSingleton } from "openclaw/plugin-sdk/text-runtime";
4
+ const XY_THREAD_BINDINGS_STATE_KEY = Symbol.for("openclaw.xyThreadBindingsState");
5
+ const state = resolveGlobalSingleton(XY_THREAD_BINDINGS_STATE_KEY, () => ({
6
+ managersByAccountId: new Map(),
7
+ bindingsByAccountSession: new Map(),
8
+ }));
9
+ function getState() {
10
+ return state;
11
+ }
12
+ function resolveBindingKey(params) {
13
+ return `${params.accountId}:${params.sessionId}`;
14
+ }
15
+ function toSessionBindingTargetKind(raw) {
16
+ return raw === "subagent" ? "subagent" : "session";
17
+ }
18
+ function toXYTargetKind(raw) {
19
+ return raw === "subagent" ? "subagent" : "session";
20
+ }
21
+ function toSessionBindingRecord(record, defaults) {
22
+ const idleExpiresAt = defaults.idleTimeoutMs > 0 ? record.lastActivityAt + defaults.idleTimeoutMs : undefined;
23
+ const maxAgeExpiresAt = defaults.maxAgeMs > 0 ? record.boundAt + defaults.maxAgeMs : undefined;
24
+ const expiresAt = idleExpiresAt != null && maxAgeExpiresAt != null
25
+ ? Math.min(idleExpiresAt, maxAgeExpiresAt)
26
+ : (idleExpiresAt ?? maxAgeExpiresAt);
27
+ return {
28
+ bindingId: resolveBindingKey({
29
+ accountId: record.accountId,
30
+ sessionId: record.sessionId,
31
+ }),
32
+ targetSessionKey: record.targetSessionKey,
33
+ targetKind: toSessionBindingTargetKind(record.targetKind),
34
+ conversation: {
35
+ channel: "xiaoyi-channel",
36
+ accountId: record.accountId,
37
+ conversationId: record.sessionId, // sessionId is the conversationId for Xiaoyi
38
+ parentConversationId: undefined, // Xiaoyi doesn't have parent conversations
39
+ },
40
+ status: "active",
41
+ boundAt: record.boundAt,
42
+ expiresAt,
43
+ metadata: {
44
+ agentId: record.agentId,
45
+ lastActivityAt: record.lastActivityAt,
46
+ idleTimeoutMs: defaults.idleTimeoutMs,
47
+ maxAgeMs: defaults.maxAgeMs,
48
+ },
49
+ };
50
+ }
51
+ /**
52
+ * Creates a thread binding manager for Xiaoyi channel.
53
+ * Based on feishu implementation but simplified for single-account mode.
54
+ */
55
+ export function createXYThreadBindingManager(params) {
56
+ const accountId = normalizeAccountId(params.accountId);
57
+ const existing = getState().managersByAccountId.get(accountId);
58
+ if (existing) {
59
+ return existing;
60
+ }
61
+ const idleTimeoutMs = resolveThreadBindingIdleTimeoutMsForChannel({
62
+ cfg: params.cfg,
63
+ channel: "xiaoyi-channel",
64
+ accountId,
65
+ });
66
+ const maxAgeMs = resolveThreadBindingMaxAgeMsForChannel({
67
+ cfg: params.cfg,
68
+ channel: "xiaoyi-channel",
69
+ accountId,
70
+ });
71
+ const manager = {
72
+ accountId,
73
+ getBySessionId: (sessionId) => getState().bindingsByAccountSession.get(resolveBindingKey({ accountId, sessionId })),
74
+ listBySessionKey: (targetSessionKey) => [...getState().bindingsByAccountSession.values()].filter((record) => record.accountId === accountId && record.targetSessionKey === targetSessionKey),
75
+ bindSession: ({ sessionId, targetKind, targetSessionKey, metadata, }) => {
76
+ const normalizedSessionId = sessionId.trim();
77
+ if (!normalizedSessionId || !targetSessionKey.trim()) {
78
+ return null;
79
+ }
80
+ const now = Date.now();
81
+ const record = {
82
+ accountId,
83
+ sessionId: normalizedSessionId,
84
+ targetKind: toXYTargetKind(targetKind),
85
+ targetSessionKey: targetSessionKey.trim(),
86
+ agentId: typeof metadata?.agentId === "string" && metadata.agentId.trim()
87
+ ? metadata.agentId.trim()
88
+ : resolveAgentIdFromSessionKey(targetSessionKey),
89
+ boundAt: now,
90
+ lastActivityAt: now,
91
+ };
92
+ getState().bindingsByAccountSession.set(resolveBindingKey({ accountId, sessionId: normalizedSessionId }), record);
93
+ return record;
94
+ },
95
+ touchSession: (sessionId, at = Date.now()) => {
96
+ const key = resolveBindingKey({ accountId, sessionId });
97
+ const existingRecord = getState().bindingsByAccountSession.get(key);
98
+ if (!existingRecord) {
99
+ return null;
100
+ }
101
+ const updated = { ...existingRecord, lastActivityAt: at };
102
+ getState().bindingsByAccountSession.set(key, updated);
103
+ return updated;
104
+ },
105
+ unbindSession: (sessionId) => {
106
+ const key = resolveBindingKey({ accountId, sessionId });
107
+ const existingRecord = getState().bindingsByAccountSession.get(key);
108
+ if (!existingRecord) {
109
+ return null;
110
+ }
111
+ getState().bindingsByAccountSession.delete(key);
112
+ return existingRecord;
113
+ },
114
+ unbindBySessionKey: (targetSessionKey) => {
115
+ const removed = [];
116
+ for (const record of [...getState().bindingsByAccountSession.values()]) {
117
+ if (record.accountId !== accountId || record.targetSessionKey !== targetSessionKey) {
118
+ continue;
119
+ }
120
+ getState().bindingsByAccountSession.delete(resolveBindingKey({ accountId, sessionId: record.sessionId }));
121
+ removed.push(record);
122
+ }
123
+ return removed;
124
+ },
125
+ stop: () => {
126
+ for (const key of [...getState().bindingsByAccountSession.keys()]) {
127
+ if (key.startsWith(`${accountId}:`)) {
128
+ getState().bindingsByAccountSession.delete(key);
129
+ }
130
+ }
131
+ getState().managersByAccountId.delete(accountId);
132
+ unregisterSessionBindingAdapter({
133
+ channel: "xiaoyi-channel",
134
+ accountId,
135
+ adapter: sessionBindingAdapter,
136
+ });
137
+ },
138
+ };
139
+ const sessionBindingAdapter = {
140
+ channel: "xiaoyi-channel",
141
+ accountId,
142
+ capabilities: {
143
+ placements: ["current"],
144
+ },
145
+ bind: async (input) => {
146
+ if (input.conversation.channel !== "xiaoyi-channel" || input.placement === "child") {
147
+ return null;
148
+ }
149
+ const bound = manager.bindSession({
150
+ sessionId: input.conversation.conversationId,
151
+ targetKind: input.targetKind,
152
+ targetSessionKey: input.targetSessionKey,
153
+ metadata: input.metadata,
154
+ });
155
+ return bound ? toSessionBindingRecord(bound, { idleTimeoutMs, maxAgeMs }) : null;
156
+ },
157
+ listBySession: (targetSessionKey) => manager
158
+ .listBySessionKey(targetSessionKey)
159
+ .map((entry) => toSessionBindingRecord(entry, { idleTimeoutMs, maxAgeMs })),
160
+ resolveByConversation: (ref) => {
161
+ if (ref.channel !== "xiaoyi-channel") {
162
+ return null;
163
+ }
164
+ const found = manager.getBySessionId(ref.conversationId);
165
+ return found ? toSessionBindingRecord(found, { idleTimeoutMs, maxAgeMs }) : null;
166
+ },
167
+ touch: (bindingId, at) => {
168
+ const sessionId = resolveThreadBindingConversationIdFromBindingId({
169
+ accountId,
170
+ bindingId,
171
+ });
172
+ if (sessionId) {
173
+ manager.touchSession(sessionId, at);
174
+ }
175
+ },
176
+ unbind: async (input) => {
177
+ if (input.targetSessionKey?.trim()) {
178
+ return manager
179
+ .unbindBySessionKey(input.targetSessionKey.trim())
180
+ .map((entry) => toSessionBindingRecord(entry, { idleTimeoutMs, maxAgeMs }));
181
+ }
182
+ const sessionId = resolveThreadBindingConversationIdFromBindingId({
183
+ accountId,
184
+ bindingId: input.bindingId,
185
+ });
186
+ if (!sessionId) {
187
+ return [];
188
+ }
189
+ const removed = manager.unbindSession(sessionId);
190
+ return removed ? [toSessionBindingRecord(removed, { idleTimeoutMs, maxAgeMs })] : [];
191
+ },
192
+ };
193
+ registerSessionBindingAdapter(sessionBindingAdapter);
194
+ getState().managersByAccountId.set(accountId, manager);
195
+ return manager;
196
+ }
197
+ /**
198
+ * Gets the thread binding manager for a given account ID.
199
+ */
200
+ export function getXYThreadBindingManager(accountId) {
201
+ return getState().managersByAccountId.get(normalizeAccountId(accountId)) ?? null;
202
+ }
203
+ /**
204
+ * Testing utilities for thread bindings.
205
+ */
206
+ export const __testing = {
207
+ resetXYThreadBindingsForTests() {
208
+ for (const manager of getState().managersByAccountId.values()) {
209
+ manager.stop();
210
+ }
211
+ getState().managersByAccountId.clear();
212
+ getState().bindingsByAccountSession.clear();
213
+ },
214
+ };
@@ -1,7 +1,6 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { sendCommand } from "../formatter.js";
3
3
  import { getCurrentSessionContext } from "./session-manager.js";
4
- import { logger } from "../utils/logger.js";
5
4
  /**
6
5
  * XY calendar event tool - creates a calendar event on user's device.
7
6
  * Requires title, dtStart (start time), and dtEnd (end time) parameters.
@@ -10,7 +9,11 @@ import { logger } from "../utils/logger.js";
10
9
  export const calendarTool = {
11
10
  name: "create_calendar_event",
12
11
  label: "Create Calendar Event",
13
- description: "在用户设备上创建日程。需要提供日程标题、开始时间和结束时间。时间格式必须为:yyyy-mm-dd hh:mm:ss(例如:2024-01-15 14:30:00)。注意:该工具执行时间较长(最多60秒),请勿重复调用,超时或失败时最多重试一次。",
12
+ description: `在用户设备上创建日程。需要提供日程标题、开始时间和结束时间。时间格式必须为:yyyy-mm-dd hh:mm:ss(例如:2024-01-15 14:30:00)。注意:该工具执行时间较长(最多60秒),请勿重复调用,超时或失败时最多重试一次。
13
+ 注意事项:使用该工具之前需获取当前真实时间
14
+
15
+ 回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。
16
+ `,
14
17
  parameters: {
15
18
  type: "object",
16
19
  properties: {
@@ -30,47 +33,25 @@ export const calendarTool = {
30
33
  required: ["title", "dtStart", "dtEnd"],
31
34
  },
32
35
  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
36
  // Validate parameters
38
37
  if (!params.title || !params.dtStart || !params.dtEnd) {
39
- logger.error(`[CALENDAR_TOOL] ❌ Missing required parameters`);
40
38
  throw new Error("Missing required parameters: title, dtStart, and dtEnd are required");
41
39
  }
42
40
  // 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
41
  const dtStartMs = new Date(params.dtStart).getTime();
47
42
  const dtEndMs = new Date(params.dtEnd).getTime();
48
43
  if (isNaN(dtStartMs) || isNaN(dtEndMs)) {
49
- logger.error(`[CALENDAR_TOOL] ❌ Invalid time format`);
50
44
  throw new Error("Invalid time format. Required format: yyyy-mm-dd hh:mm:ss (e.g., 2024-01-15 14:30:00)");
51
45
  }
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
46
  // Get session context
56
- logger.log(`[CALENDAR_TOOL] 🔍 Attempting to get session context...`);
57
47
  const sessionContext = getCurrentSessionContext();
58
48
  if (!sessionContext) {
59
- logger.error(`[CALENDAR_TOOL] ❌ FAILED: No active session found!`);
60
- logger.error(`[CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
61
49
  throw new Error("No active XY session found. Calendar tool can only be used during an active conversation.");
62
50
  }
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
51
  const { config, sessionId, taskId, messageId } = sessionContext;
68
52
  // Get WebSocket manager
69
- logger.log(`[CALENDAR_TOOL] 🔌 Getting WebSocket manager...`);
70
53
  const wsManager = getXYWebSocketManager(config);
71
- logger.log(`[CALENDAR_TOOL] ✅ WebSocket manager obtained`);
72
54
  // Build CreateCalendarEvent command
73
- logger.log(`[CALENDAR_TOOL] 📦 Building CreateCalendarEvent command...`);
74
55
  const command = {
75
56
  header: {
76
57
  namespace: "Common",
@@ -106,25 +87,17 @@ export const calendarTool = {
106
87
  },
107
88
  };
108
89
  // 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
90
  return new Promise((resolve, reject) => {
112
91
  const timeout = setTimeout(() => {
113
- logger.error(`[CALENDAR_TOOL] ⏰ Timeout: No response received within 60 seconds`);
114
92
  wsManager.off("data-event", handler);
115
93
  reject(new Error("创建日程超时(60秒)"));
116
94
  }, 60000);
117
95
  // Listen for data events from WebSocket
118
96
  const handler = (event) => {
119
- logger.log(`[CALENDAR_TOOL] 📨 Received data event:`, JSON.stringify(event));
120
97
  if (event.intentName === "CreateCalendarEvent") {
121
- logger.log(`[CALENDAR_TOOL] 🎯 CreateCalendarEvent event received`);
122
- logger.log(`[CALENDAR_TOOL] - status: ${event.status}`);
123
98
  clearTimeout(timeout);
124
99
  wsManager.off("data-event", handler);
125
100
  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
101
  resolve({
129
102
  content: [
130
103
  {
@@ -135,17 +108,13 @@ export const calendarTool = {
135
108
  });
136
109
  }
137
110
  else {
138
- logger.error(`[CALENDAR_TOOL] ❌ Calendar event creation failed`);
139
- logger.error(`[CALENDAR_TOOL] - status: ${event.status}`);
140
111
  reject(new Error(`创建日程失败: ${event.status}`));
141
112
  }
142
113
  }
143
114
  };
144
115
  // Register event handler
145
- logger.log(`[CALENDAR_TOOL] 📡 Registering data-event handler on WebSocket manager`);
146
116
  wsManager.on("data-event", handler);
147
117
  // Send the command
148
- logger.log(`[CALENDAR_TOOL] 📤 Sending CreateCalendarEvent command...`);
149
118
  sendCommand({
150
119
  config,
151
120
  sessionId,
@@ -154,10 +123,8 @@ export const calendarTool = {
154
123
  command,
155
124
  })
156
125
  .then(() => {
157
- logger.log(`[CALENDAR_TOOL] ✅ Command sent successfully, waiting for response...`);
158
126
  })
159
127
  .catch((error) => {
160
- logger.error(`[CALENDAR_TOOL] ❌ Failed to send command:`, error);
161
128
  clearTimeout(timeout);
162
129
  wsManager.off("data-event", handler);
163
130
  reject(error);
@@ -1,7 +1,6 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { sendCommand } from "../formatter.js";
3
3
  import { getCurrentSessionContext } from "./session-manager.js";
4
- import { logger } from "../utils/logger.js";
5
4
  /**
6
5
  * XY call phone tool - makes a phone call on user's device.
7
6
  * Requires phoneNumber parameter and optional slotId (0 for primary SIM, 1 for secondary SIM).
@@ -9,7 +8,7 @@ import { logger } from "../utils/logger.js";
9
8
  export const callPhoneTool = {
10
9
  name: "call_phone",
11
10
  label: "Call Phone",
12
- description: "拨打电话。需要提供要拨打的电话号码。slotId参数可选,默认为0(主卡),如果用户明确要求使用副卡则设置为1。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
11
+ description: "拨打电话。需要提供要拨打的电话号码。slotId参数可选,默认为0(主卡),如果用户明确要求使用副卡则设置为1。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。",
13
12
  parameters: {
14
13
  type: "object",
15
14
  properties: {
@@ -26,43 +25,25 @@ export const callPhoneTool = {
26
25
  required: ["phoneNumber"],
27
26
  },
28
27
  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
28
  // Validate phoneNumber parameter
34
29
  if (!params.phoneNumber || typeof params.phoneNumber !== "string" || params.phoneNumber.trim() === "") {
35
- logger.error(`[CALL_PHONE_TOOL] ❌ Missing or invalid phoneNumber parameter`);
36
30
  throw new Error("Missing required parameter: phoneNumber must be a non-empty string");
37
31
  }
38
32
  // Set default slotId if not provided
39
33
  const slotId = params.slotId !== undefined && params.slotId !== null ? params.slotId : 0;
40
34
  // Validate slotId (must be 0 or 1)
41
35
  if (slotId !== 0 && slotId !== 1) {
42
- logger.error(`[CALL_PHONE_TOOL] ❌ Invalid slotId: ${slotId}`);
43
36
  throw new Error("Invalid slotId: must be 0 (primary SIM) or 1 (secondary SIM)");
44
37
  }
45
- logger.log(`[CALL_PHONE_TOOL] 📞 Preparing to call phone number: ${params.phoneNumber}`);
46
- logger.log(`[CALL_PHONE_TOOL] - slotId: ${slotId} (${slotId === 0 ? "主卡" : "副卡"})`);
47
38
  // Get session context
48
- logger.log(`[CALL_PHONE_TOOL] 🔍 Attempting to get session context...`);
49
39
  const sessionContext = getCurrentSessionContext();
50
40
  if (!sessionContext) {
51
- logger.error(`[CALL_PHONE_TOOL] ❌ FAILED: No active session found!`);
52
- logger.error(`[CALL_PHONE_TOOL] - toolCallId: ${toolCallId}`);
53
41
  throw new Error("No active XY session found. Call phone tool can only be used during an active conversation.");
54
42
  }
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
43
  const { config, sessionId, taskId, messageId } = sessionContext;
60
44
  // Get WebSocket manager
61
- logger.log(`[CALL_PHONE_TOOL] 🔌 Getting WebSocket manager...`);
62
45
  const wsManager = getXYWebSocketManager(config);
63
- logger.log(`[CALL_PHONE_TOOL] ✅ WebSocket manager obtained`);
64
46
  // Build StartCall command
65
- logger.log(`[CALL_PHONE_TOOL] 📦 Building StartCall command...`);
66
47
  const command = {
67
48
  header: {
68
49
  namespace: "Common",
@@ -97,34 +78,22 @@ export const callPhoneTool = {
97
78
  pageControlRelated: false,
98
79
  },
99
80
  };
100
- logger.log(`[CALL_PHONE_TOOL] 📋 Command details:`, JSON.stringify(command, null, 2));
101
81
  // 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
82
  return new Promise((resolve, reject) => {
105
83
  const timeout = setTimeout(() => {
106
- logger.error(`[CALL_PHONE_TOOL] ⏰ Timeout: No response received within 60 seconds`);
107
84
  wsManager.off("data-event", handler);
108
85
  reject(new Error("拨打电话超时(60秒)"));
109
86
  }, 60000);
110
87
  // Listen for data events from WebSocket
111
88
  const handler = (event) => {
112
- logger.log(`[CALL_PHONE_TOOL] 📨 Received data event:`, JSON.stringify(event));
113
89
  if (event.intentName === "StartCall") {
114
- logger.log(`[CALL_PHONE_TOOL] 🎯 StartCall event received`);
115
- logger.log(`[CALL_PHONE_TOOL] - status: ${event.status}`);
116
90
  clearTimeout(timeout);
117
91
  wsManager.off("data-event", handler);
118
92
  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
93
  // Check for error code in outputs
122
94
  const code = event.outputs.code !== undefined ? event.outputs.code : null;
123
95
  if (code !== null && code !== 0) {
124
- logger.error(`[CALL_PHONE_TOOL] ❌ Device returned error`);
125
- logger.error(`[CALL_PHONE_TOOL] - code: ${code}`);
126
96
  const errorMsg = event.outputs.errorMsg || event.outputs.errMsg || "未知错误";
127
- logger.error(`[CALL_PHONE_TOOL] - errorMsg: ${errorMsg}`);
128
97
  reject(new Error(`拨打电话失败: ${errorMsg} (错误代码: ${code})`));
129
98
  return;
130
99
  }
@@ -136,9 +105,6 @@ export const callPhoneTool = {
136
105
  slotId: slotId,
137
106
  message: "电话拨打成功",
138
107
  };
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
108
  resolve({
143
109
  content: [
144
110
  {
@@ -149,19 +115,14 @@ export const callPhoneTool = {
149
115
  });
150
116
  }
151
117
  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
118
  const errorDetail = event.outputs ? JSON.stringify(event.outputs) : event.status;
156
119
  reject(new Error(`拨打电话失败: ${errorDetail}`));
157
120
  }
158
121
  }
159
122
  };
160
123
  // Register event handler
161
- logger.log(`[CALL_PHONE_TOOL] 📡 Registering data-event handler on WebSocket manager`);
162
124
  wsManager.on("data-event", handler);
163
125
  // Send the command
164
- logger.log(`[CALL_PHONE_TOOL] 📤 Sending StartCall command...`);
165
126
  sendCommand({
166
127
  config,
167
128
  sessionId,
@@ -170,10 +131,8 @@ export const callPhoneTool = {
170
131
  command,
171
132
  })
172
133
  .then(() => {
173
- logger.log(`[CALL_PHONE_TOOL] ✅ Command sent successfully, waiting for response...`);
174
134
  })
175
135
  .catch((error) => {
176
- logger.error(`[CALL_PHONE_TOOL] ❌ Failed to send command:`, error);
177
136
  clearTimeout(timeout);
178
137
  wsManager.off("data-event", handler);
179
138
  reject(error);