@ynhcj/xiaoyi-channel 0.0.41-beta → 0.0.41-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 (76) hide show
  1. package/dist/index.d.ts +0 -2
  2. package/dist/index.js +42 -2
  3. package/dist/src/bot.d.ts +1 -0
  4. package/dist/src/bot.js +97 -60
  5. package/dist/src/channel.js +30 -4
  6. package/dist/src/client.js +0 -9
  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 +16 -0
  18. package/dist/src/formatter.js +59 -30
  19. package/dist/src/heartbeat.js +0 -4
  20. package/dist/src/monitor.js +8 -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 +3 -21
  25. package/dist/src/parser.d.ts +13 -0
  26. package/dist/src/parser.js +38 -0
  27. package/dist/src/push.d.ts +2 -1
  28. package/dist/src/push.js +6 -34
  29. package/dist/src/reply-dispatcher.d.ts +4 -0
  30. package/dist/src/reply-dispatcher.js +46 -8
  31. package/dist/src/steer-injector.d.ts +16 -0
  32. package/dist/src/steer-injector.js +74 -0
  33. package/dist/src/thread-bindings.d.ts +54 -0
  34. package/dist/src/thread-bindings.js +214 -0
  35. package/dist/src/tools/calendar-tool.js +2 -37
  36. package/dist/src/tools/call-phone-tool.js +3 -60
  37. package/dist/src/tools/create-alarm-tool.js +8 -109
  38. package/dist/src/tools/delete-alarm-tool.js +5 -69
  39. package/dist/src/tools/device-tool-map.d.ts +4 -0
  40. package/dist/src/tools/device-tool-map.js +23 -0
  41. package/dist/src/tools/image-reading-tool.d.ts +5 -0
  42. package/dist/src/tools/image-reading-tool.js +328 -0
  43. package/dist/src/tools/location-tool.js +6 -40
  44. package/dist/src/tools/modify-alarm-tool.js +8 -114
  45. package/dist/src/tools/modify-note-tool.js +3 -41
  46. package/dist/src/tools/note-tool.js +4 -16
  47. package/dist/src/tools/search-alarm-tool.js +12 -118
  48. package/dist/src/tools/search-calendar-tool.js +4 -81
  49. package/dist/src/tools/search-contact-tool.js +2 -55
  50. package/dist/src/tools/search-file-tool.js +4 -61
  51. package/dist/src/tools/search-message-tool.js +2 -59
  52. package/dist/src/tools/search-note-tool.js +4 -22
  53. package/dist/src/tools/search-photo-gallery-tool.js +8 -57
  54. package/dist/src/tools/send-command-to-car-tool.d.ts +5 -0
  55. package/dist/src/tools/send-command-to-car-tool.js +85 -0
  56. package/dist/src/tools/send-file-to-user-tool.js +1 -39
  57. package/dist/src/tools/send-message-tool.js +5 -56
  58. package/dist/src/tools/session-manager.d.ts +1 -0
  59. package/dist/src/tools/session-manager.js +0 -45
  60. package/dist/src/tools/timestamp-to-utc8-tool.d.ts +12 -0
  61. package/dist/src/tools/timestamp-to-utc8-tool.js +104 -0
  62. package/dist/src/tools/upload-file-tool.js +0 -49
  63. package/dist/src/tools/upload-photo-tool.js +0 -42
  64. package/dist/src/tools/view-push-result-tool.js +0 -11
  65. package/dist/src/tools/xiaoyi-collection-tool.js +4 -82
  66. package/dist/src/tools/xiaoyi-gui-tool.js +0 -34
  67. package/dist/src/trigger-handler.d.ts +3 -0
  68. package/dist/src/trigger-handler.js +18 -50
  69. package/dist/src/utils/runtime-manager.d.ts +7 -0
  70. package/dist/src/utils/runtime-manager.js +42 -0
  71. package/dist/src/websocket.js +33 -13
  72. package/package.json +3 -4
  73. package/dist/src/tools/search-photo-tool.d.ts +0 -9
  74. package/dist/src/tools/search-photo-tool.js +0 -270
  75. package/dist/src/utils/session.d.ts +0 -34
  76. package/dist/src/utils/session.js +0 -50
@@ -0,0 +1,22 @@
1
+ // CSPL Hook 常量与类型定义
2
+ export const MIN_TEXT_LENGTH = 0;
3
+ export const MAX_TEXT_LENGTH = 4096;
4
+ export const MAX_TOTAL_LENGTH = 40960;
5
+ export const regex = /[^\u4e00-\u9fa5a-zA-Z0-9\s\.,!?;:,。!?;:""\'\'()()\[\]【】]/;
6
+ export const DEFAULT_HTTP_PORT = 443;
7
+ export const HTTP_STATUS_BAD_REQUEST = 400;
8
+ export const ENV_FILE_PATH = "/home/sandbox/.openclaw/.xiaoyienv";
9
+ export const API_URL_SUFFIX = "/celia-claw/v1/rest-api/skill/execute";
10
+ export const REQUIRED_ENV_VARS = ["PERSONAL-API-KEY", "PERSONAL-UID", "SERVICE_URL"];
11
+ // 工具白名单
12
+ export const ALLOWED_TOOLS = ["web_fetch", "read", "pdf"];
13
+ // 静态配置(非敏感,敏感值从 ENV 文件读取)
14
+ export const CSPL_STATIC_CONFIG = {
15
+ api: { timeout: 5000 },
16
+ skillId: "skill-scope",
17
+ requestFrom: "openclaw",
18
+ textSource: "question",
19
+ action: "TOOL_OUTPUT_SCAN",
20
+ };
21
+ // Steer 注入常量
22
+ export const STEER_ABORT_MESSAGE = "当前行为存在安全隐患,终止所有操作,并且在最终回复中说明,识别到当前流程中存在潜在安全风险,已帮您中止当前流程,请清空上下文后重试";
@@ -0,0 +1,10 @@
1
+ export declare function filterText(text: string): string;
2
+ export declare function validateAndTruncateText(text: string, maxLength: number): {
3
+ text: string;
4
+ truncated: boolean;
5
+ };
6
+ export declare function extractResultText(event: any, toolName: string): string;
7
+ export declare function processText(resultText: string): string;
8
+ export declare function parseSecurityResult(response: any): {
9
+ status: "ACCEPT" | "REJECT";
10
+ };
@@ -0,0 +1,57 @@
1
+ // CSPL Hook 工具函数
2
+ import { MAX_TEXT_LENGTH, regex } from "./constants.js";
3
+ export function filterText(text) {
4
+ if (!text)
5
+ return "";
6
+ return text.replace(new RegExp(regex.source, "g"), "");
7
+ }
8
+ export function validateAndTruncateText(text, maxLength) {
9
+ if (text.length > maxLength) {
10
+ const halfMaxLength = Math.floor(maxLength / 2);
11
+ const startText = text.substring(0, halfMaxLength);
12
+ const endText = text.substring(text.length - halfMaxLength);
13
+ return { text: startText + endText, truncated: true };
14
+ }
15
+ return { text, truncated: false };
16
+ }
17
+ export function extractResultText(event, toolName) {
18
+ const resultTexts = [];
19
+ if (toolName === "web_fetch") {
20
+ if (event.result?.details?.text) {
21
+ resultTexts.push(event.result.details.text);
22
+ }
23
+ return resultTexts.length > 0 ? resultTexts.join("; ") : "";
24
+ }
25
+ if (event.result?.content && Array.isArray(event.result.content)) {
26
+ for (const item of event.result.content) {
27
+ if (item?.text) {
28
+ resultTexts.push(item.text);
29
+ }
30
+ }
31
+ }
32
+ return resultTexts.length > 0 ? resultTexts.join("; ") : "";
33
+ }
34
+ export function processText(resultText) {
35
+ const questionText = filterText(resultText);
36
+ const { text: finalText } = validateAndTruncateText(questionText, MAX_TEXT_LENGTH);
37
+ return finalText;
38
+ }
39
+ export function parseSecurityResult(response) {
40
+ if (response === null || response === undefined) {
41
+ throw new Error("Response is null or undefined");
42
+ }
43
+ if (!response.data || typeof response.data !== "object") {
44
+ throw new Error("Response.data is missing or not an object");
45
+ }
46
+ const securityResult = response.data.securityResult;
47
+ if (typeof securityResult !== "string") {
48
+ throw new Error("Response.data.securityResult is missing or not a string");
49
+ }
50
+ if (securityResult !== securityResult.trim()) {
51
+ throw new Error("Response.data.securityResult contains leading or trailing spaces");
52
+ }
53
+ if (securityResult !== "ACCEPT" && securityResult !== "REJECT") {
54
+ throw new Error(`Response.data.securityResult must be "ACCEPT" or "REJECT". Actual: "${securityResult}"`);
55
+ }
56
+ return { status: securityResult };
57
+ }
@@ -12,6 +12,11 @@ export declare class XYFileUploadService {
12
12
  * Returns the objectId (as fileId) for use in A2A messages.
13
13
  */
14
14
  uploadFile(filePath: string, objectType?: string): Promise<string>;
15
+ /**
16
+ * Upload a file and return its publicly accessible URL.
17
+ * Uses completeAndQuery endpoint to get the file URL directly.
18
+ */
19
+ uploadFileAndGetUrl(filePath: string, objectType?: string): Promise<string>;
15
20
  /**
16
21
  * Upload multiple files and return their file IDs.
17
22
  */
@@ -55,12 +55,10 @@ export class XYFileUploadService {
55
55
  throw new Error(`Prepare failed: HTTP ${prepareResp.status}`);
56
56
  }
57
57
  const prepareData = await prepareResp.json();
58
- console.log(`[XY File Upload] Prepare response:`, JSON.stringify(prepareData, null, 2));
59
58
  if (prepareData.code !== "0") {
60
59
  throw new Error(`Prepare failed: ${prepareData.desc}`);
61
60
  }
62
61
  const { objectId, draftId, uploadInfos } = prepareData;
63
- console.log(`[XY File Upload] Prepare complete: objectId=${objectId}, draftId=${draftId}`);
64
62
  // Phase 2: Upload
65
63
  console.log(`[XY File Upload] Phase 2: Upload file data`);
66
64
  const uploadInfo = uploadInfos[0]; // Single-part upload
@@ -69,11 +67,8 @@ export class XYFileUploadService {
69
67
  headers: uploadInfo.headers,
70
68
  body: fileBuffer,
71
69
  });
72
- console.log(`[XY File Upload] Upload response status: ${uploadResp.status}, url: ${uploadInfo.url}`);
73
- console.log(`[XY File Upload] Upload response headers:`, JSON.stringify(Object.fromEntries(uploadResp.headers.entries()), null, 2));
74
70
  if (!uploadResp.ok) {
75
71
  const uploadErrorText = await uploadResp.text();
76
- console.log(`[XY File Upload] Upload error response:`, uploadErrorText);
77
72
  throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
78
73
  }
79
74
  console.log(`[XY File Upload] Upload complete`);
@@ -96,7 +91,6 @@ export class XYFileUploadService {
96
91
  throw new Error(`Complete failed: HTTP ${completeResp.status}`);
97
92
  }
98
93
  const completeData = await completeResp.json();
99
- console.log(`[XY File Upload] Complete response:`, JSON.stringify(completeData, null, 2));
100
94
  console.log(`[XY File Upload] File upload successful: ${fileName} → objectId=${objectId}`);
101
95
  return objectId;
102
96
  }
@@ -105,6 +99,94 @@ export class XYFileUploadService {
105
99
  return "";
106
100
  }
107
101
  }
102
+ /**
103
+ * Upload a file and return its publicly accessible URL.
104
+ * Uses completeAndQuery endpoint to get the file URL directly.
105
+ */
106
+ async uploadFileAndGetUrl(filePath, objectType = "TEMPORARY_MATERIAL_DOC") {
107
+ try {
108
+ // Read file
109
+ const fileBuffer = await fs.readFile(filePath);
110
+ const fileName = path.basename(filePath);
111
+ const fileSha256 = calculateSHA256(fileBuffer);
112
+ const fileSize = fileBuffer.length;
113
+ // Phase 1: Prepare
114
+ console.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
115
+ const prepareResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/prepare`, {
116
+ method: "POST",
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ "x-uid": this.uid,
120
+ "x-api-key": this.apiKey,
121
+ "x-request-from": "openclaw",
122
+ },
123
+ body: JSON.stringify({
124
+ objectType,
125
+ fileName,
126
+ fileSha256,
127
+ fileSize,
128
+ fileOwnerInfo: {
129
+ uid: this.uid,
130
+ teamId: this.uid,
131
+ },
132
+ useEdge: false,
133
+ }),
134
+ });
135
+ if (!prepareResp.ok) {
136
+ throw new Error(`Prepare failed: HTTP ${prepareResp.status}`);
137
+ }
138
+ const prepareData = await prepareResp.json();
139
+ if (prepareData.code !== "0") {
140
+ throw new Error(`Prepare failed: ${prepareData.desc}`);
141
+ }
142
+ const { objectId, draftId, uploadInfos } = prepareData;
143
+ console.log(`[XY File Upload] Prepare complete: objectId=${objectId}, draftId=${draftId}`);
144
+ // Phase 2: Upload
145
+ console.log(`[XY File Upload] Phase 2: Upload file data`);
146
+ const uploadInfo = uploadInfos[0]; // Single-part upload
147
+ const uploadResp = await fetch(uploadInfo.url, {
148
+ method: uploadInfo.method,
149
+ headers: uploadInfo.headers,
150
+ body: fileBuffer,
151
+ });
152
+ console.log(`[XY File Upload] Upload response status: ${uploadResp.status}`);
153
+ if (!uploadResp.ok) {
154
+ const uploadErrorText = await uploadResp.text();
155
+ throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
156
+ }
157
+ console.log(`[XY File Upload] Upload complete`);
158
+ // Phase 3: CompleteAndQuery - get file URL
159
+ console.log(`[XY File Upload] Phase 3: CompleteAndQuery to get file URL`);
160
+ const completeResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/completeAndQuery`, {
161
+ method: "POST",
162
+ headers: {
163
+ "Content-Type": "application/json",
164
+ "x-uid": this.uid,
165
+ "x-api-key": this.apiKey,
166
+ "x-request-from": "openclaw",
167
+ },
168
+ body: JSON.stringify({
169
+ objectId,
170
+ draftId,
171
+ }),
172
+ });
173
+ if (!completeResp.ok) {
174
+ throw new Error(`CompleteAndQuery failed: HTTP ${completeResp.status}`);
175
+ }
176
+ const completeData = await completeResp.json();
177
+ // Extract file URL from response
178
+ const fileUrl = completeData?.fileDetailInfo?.url || "";
179
+ if (!fileUrl) {
180
+ throw new Error("No file URL returned from completeAndQuery");
181
+ }
182
+ console.log(`[XY File Upload] File upload successful`);
183
+ return fileUrl;
184
+ }
185
+ catch (error) {
186
+ console.error(`[XY File Upload] File upload with URL retrieval failed for ${filePath}:`, error);
187
+ throw error;
188
+ }
189
+ }
108
190
  /**
109
191
  * Upload multiple files and return their file IDs.
110
192
  */
@@ -15,6 +15,8 @@ export interface SendA2AResponseParams {
15
15
  fileType: string;
16
16
  fileId: string;
17
17
  }>;
18
+ errorCode?: number | string;
19
+ errorMessage?: string;
18
20
  }
19
21
  /**
20
22
  * Send an A2A artifact update response.
@@ -92,3 +94,17 @@ export interface SendTasksCancelResponseParams {
92
94
  * Send a tasks/cancel response.
93
95
  */
94
96
  export declare function sendTasksCancelResponse(params: SendTasksCancelResponseParams): Promise<void>;
97
+ /**
98
+ * Parameters for sending a Trigger response.
99
+ */
100
+ export interface SendTriggerResponseParams {
101
+ config: XYChannelConfig;
102
+ sessionId: string;
103
+ taskId: string;
104
+ messageId: string;
105
+ content: string;
106
+ }
107
+ /**
108
+ * Send a Trigger response with pushData content.
109
+ */
110
+ export declare function sendTriggerResponse(params: SendTriggerResponseParams): Promise<void>;
@@ -6,10 +6,10 @@ import { getXYRuntime } from "./runtime.js";
6
6
  * Send an A2A artifact update response.
7
7
  */
8
8
  export async function sendA2AResponse(params) {
9
- const { config, sessionId, taskId, messageId, text, append, final, files } = params;
9
+ const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage } = params;
10
10
  const runtime = getXYRuntime();
11
11
  const log = runtime?.log ?? console.log;
12
- const error = runtime?.error ?? console.error;
12
+ const errorFn = runtime?.error ?? console.error;
13
13
  // Build artifact update event
14
14
  const artifact = {
15
15
  taskId,
@@ -42,6 +42,14 @@ export async function sendA2AResponse(params) {
42
42
  id: messageId,
43
43
  result: artifact,
44
44
  };
45
+ // 🔑 添加 error 字段(仅当提供 errorCode 时)
46
+ if (errorCode !== undefined) {
47
+ jsonRpcResponse.error = {
48
+ code: errorCode,
49
+ message: errorMessage ?? "任务执行异常,请重试",
50
+ };
51
+ log(`[A2A_RESPONSE] ⚠️ Including error code: ${errorCode}`);
52
+ }
45
53
  // Send via WebSocket
46
54
  const wsManager = getXYWebSocketManager(config);
47
55
  const outboundMessage = {
@@ -52,18 +60,11 @@ export async function sendA2AResponse(params) {
52
60
  msgDetail: JSON.stringify(jsonRpcResponse),
53
61
  };
54
62
  // 📋 Log complete response body
55
- log(`[A2A_RESPONSE] 📤 Sending A2A artifact-update response:`);
56
- log(`[A2A_RESPONSE] - sessionId: ${sessionId}`);
57
- log(`[A2A_RESPONSE] - taskId: ${taskId}`);
58
- log(`[A2A_RESPONSE] - messageId: ${messageId}`);
63
+ log(`[A2A_RESPONSE] 📤 Sending A2A artifact-update response: taskId: ${taskId}`);
59
64
  log(`[A2A_RESPONSE] - append: ${append}`);
60
65
  log(`[A2A_RESPONSE] - final: ${final}`);
61
- log(`[A2A_RESPONSE] - text length: ${text?.length ?? 0}`);
66
+ log(`[A2A_RESPONSE] - text: ${text.length <= 10 ? text : text.slice(0, 5) + '***' + text.slice(-5)}`);
62
67
  log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
63
- log(`[A2A_RESPONSE] 📦 Complete outbound message:`);
64
- log(JSON.stringify(outboundMessage, null, 2));
65
- log(`[A2A_RESPONSE] 📦 JSON-RPC response body:`);
66
- log(JSON.stringify(jsonRpcResponse, null, 2));
67
68
  await wsManager.sendMessage(sessionId, outboundMessage);
68
69
  log(`[A2A_RESPONSE] ✅ Message sent successfully`);
69
70
  }
@@ -106,9 +107,7 @@ export async function sendReasoningTextUpdate(params) {
106
107
  taskId,
107
108
  msgDetail: JSON.stringify(jsonRpcResponse),
108
109
  };
109
- log(`[REASONING_TEXT] 📤 Sending reasoningText update: sessionId=${sessionId}, taskId=${taskId}, text.length=${text.length}`);
110
110
  await wsManager.sendMessage(sessionId, outboundMessage);
111
- log(`[REASONING_TEXT] ✅ Sent successfully`);
112
111
  }
113
112
  /**
114
113
  * Send an A2A task status update.
@@ -154,17 +153,9 @@ export async function sendStatusUpdate(params) {
154
153
  };
155
154
  // 📋 Log complete response body
156
155
  log(`[A2A_STATUS] 📤 Sending A2A status-update:`);
157
- log(`[A2A_STATUS] - sessionId: ${sessionId}`);
158
156
  log(`[A2A_STATUS] - taskId: ${taskId}`);
159
- log(`[A2A_STATUS] - messageId: ${messageId}`);
160
- log(`[A2A_STATUS] - state: ${state}`);
161
157
  log(`[A2A_STATUS] - text: "${text}"`);
162
- log(`[A2A_STATUS] 📦 Complete outbound message:`);
163
- log(JSON.stringify(outboundMessage, null, 2));
164
- log(`[A2A_STATUS] 📦 JSON-RPC response body:`);
165
- log(JSON.stringify(jsonRpcResponse, null, 2));
166
158
  await wsManager.sendMessage(sessionId, outboundMessage);
167
- log(`[A2A_STATUS] ✅ Status update sent successfully`);
168
159
  }
169
160
  /**
170
161
  * Send a command as an artifact update (final=false).
@@ -210,15 +201,7 @@ export async function sendCommand(params) {
210
201
  msgDetail: JSON.stringify(jsonRpcResponse),
211
202
  };
212
203
  // 📋 Log complete response body
213
- log(`[A2A_COMMAND] 📤 Sending A2A command:`);
214
- log(`[A2A_COMMAND] - sessionId: ${sessionId}`);
215
- log(`[A2A_COMMAND] - taskId: ${taskId}`);
216
- log(`[A2A_COMMAND] - messageId: ${messageId}`);
217
- log(`[A2A_COMMAND] - command: ${command.header.namespace}::${command.header.name}`);
218
- log(`[A2A_COMMAND] 📦 Complete outbound message:`);
219
- log(JSON.stringify(outboundMessage, null, 2));
220
- log(`[A2A_COMMAND] 📦 JSON-RPC response body:`);
221
- log(JSON.stringify(jsonRpcResponse, null, 2));
204
+ log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${taskId}`);
222
205
  await wsManager.sendMessage(sessionId, outboundMessage);
223
206
  log(`[A2A_COMMAND] ✅ Command sent successfully`);
224
207
  }
@@ -293,3 +276,49 @@ export async function sendTasksCancelResponse(params) {
293
276
  await wsManager.sendMessage(sessionId, outboundMessage);
294
277
  log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
295
278
  }
279
+ /**
280
+ * Send a Trigger response with pushData content.
281
+ */
282
+ export async function sendTriggerResponse(params) {
283
+ const { config, sessionId, taskId, messageId, content } = params;
284
+ const runtime = getXYRuntime();
285
+ const log = runtime?.log ?? console.log;
286
+ const error = runtime?.error ?? console.error;
287
+ // Build JSON-RPC response for Trigger
288
+ const jsonRpcResponse = {
289
+ jsonrpc: "2.0",
290
+ id: messageId,
291
+ result: {
292
+ taskId: taskId,
293
+ kind: "artifact-update",
294
+ append: false,
295
+ lastChunk: true,
296
+ final: true,
297
+ artifact: {
298
+ artifactId: uuidv4(),
299
+ parts: [
300
+ {
301
+ kind: "text",
302
+ text: content,
303
+ },
304
+ ],
305
+ },
306
+ },
307
+ error: {
308
+ code: 0,
309
+ message: "",
310
+ },
311
+ };
312
+ // Send via WebSocket
313
+ const wsManager = getXYWebSocketManager(config);
314
+ const outboundMessage = {
315
+ msgType: "agent_response",
316
+ agentId: config.agentId,
317
+ sessionId,
318
+ taskId,
319
+ msgDetail: JSON.stringify(jsonRpcResponse),
320
+ };
321
+ log(`[TRIGGER_RESPONSE] Sending Trigger response: sessionId=${sessionId}, taskId=${taskId}`);
322
+ await wsManager.sendMessage(sessionId, outboundMessage);
323
+ log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
324
+ }
@@ -72,18 +72,14 @@ export class HeartbeatManager {
72
72
  }
73
73
  try {
74
74
  // Send application-level heartbeat message
75
- console.log(`[WS-${this.serverName}-SEND] Sending heartbeat frame:`, this.config.message);
76
75
  this.ws.send(this.config.message);
77
- console.log(`[WS-${this.serverName}-SEND] Heartbeat message sent, size: ${this.config.message.length} bytes`);
78
76
  // Send protocol-level ping
79
77
  this.ws.ping();
80
- console.log(`[WS-${this.serverName}-SEND] Protocol-level ping sent`);
81
78
  // Setup timeout timer
82
79
  this.timeoutTimer = setTimeout(() => {
83
80
  this.error(`Heartbeat timeout for ${this.serverName}`);
84
81
  this.onTimeout();
85
82
  }, this.config.timeout);
86
- this.log(`[DEBUG] Heartbeat sent for ${this.serverName}`);
87
83
  }
88
84
  catch (error) {
89
85
  this.error(`Failed to send heartbeat for ${this.serverName}:`, error);
@@ -4,6 +4,7 @@ import { handleXYMessage } from "./bot.js";
4
4
  import { parseA2AMessage } from "./parser.js";
5
5
  import { hasActiveTask } from "./task-manager.js";
6
6
  import { handleTriggerEvent } from "./trigger-handler.js";
7
+ import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
7
8
  /**
8
9
  * Per-session serial queue that ensures messages from the same session are processed
9
10
  * in arrival order while allowing different sessions to run concurrently.
@@ -67,7 +68,7 @@ export async function monitorXYProvider(opts = {}) {
67
68
  // Event handlers (defined early so they can be referenced in cleanup)
68
69
  const messageHandler = (message, sessionId, serverId) => {
69
70
  const messageKey = `${sessionId}::${message.id}`;
70
- log(`[MONITOR-HANDLER] ####### messageHandler triggered: serverId=${serverId}, sessionId=${sessionId}, messageId=${message.id} #######`);
71
+ log(`[MONITOR-HANDLER] ####### messageHandler triggered: sessionId=${sessionId}, messageId=${message.id} #######`);
71
72
  // ✅ Report health: received a message
72
73
  trackEvent?.();
73
74
  // Check for duplicate message handling
@@ -75,17 +76,15 @@ export async function monitorXYProvider(opts = {}) {
75
76
  error(`[MONITOR-HANDLER] ⚠️ WARNING: Duplicate message detected! messageKey=${messageKey}, this may cause duplicate dispatchers!`);
76
77
  }
77
78
  activeMessages.add(messageKey);
78
- log(`[MONITOR-HANDLER] 📝 Active messages count: ${activeMessages.size}, messageKey: ${messageKey}`);
79
79
  const task = async () => {
80
80
  try {
81
- log(`[MONITOR-HANDLER] 🚀 Starting handleXYMessage for messageKey=${messageKey}`);
82
81
  await handleXYMessage({
83
82
  cfg,
84
83
  runtime,
85
84
  message,
86
85
  accountId, // ✅ Pass accountId ("default")
86
+ webSocketSessionId: sessionId, // ✅ 传递 WebSocket 层级的 sessionId
87
87
  });
88
- log(`[MONITOR-HANDLER] ✅ Completed handleXYMessage for messageKey=${messageKey}`);
89
88
  }
90
89
  catch (err) {
91
90
  // ✅ Only log error, don't re-throw to prevent gateway restart
@@ -94,7 +93,6 @@ export async function monitorXYProvider(opts = {}) {
94
93
  finally {
95
94
  // Remove from active messages when done
96
95
  activeMessages.delete(messageKey);
97
- log(`[MONITOR-HANDLER] 🧹 Cleaned up messageKey=${messageKey}, remaining active: ${activeMessages.size}`);
98
96
  }
99
97
  };
100
98
  // 🔑 核心改造:检测steer模式
@@ -107,7 +105,6 @@ export async function monitorXYProvider(opts = {}) {
107
105
  // Steer模式且有活跃任务:不入队列,直接并发执行
108
106
  log(`[MONITOR-HANDLER] 🔄 STEER MODE: Executing concurrently for messageKey=${messageKey}`);
109
107
  log(`[MONITOR-HANDLER] - sessionId: ${parsed.sessionId}`);
110
- log(`[MONITOR-HANDLER] - Bypassing queue to allow message insertion`);
111
108
  void task().catch((err) => {
112
109
  error(`XY gateway: concurrent steer task failed for ${messageKey}: ${String(err)}`);
113
110
  activeMessages.delete(messageKey);
@@ -115,7 +112,6 @@ export async function monitorXYProvider(opts = {}) {
115
112
  }
116
113
  else {
117
114
  // 正常模式:入队列串行执行
118
- log(`[MONITOR-HANDLER] 📋 NORMAL MODE: Enqueuing for messageKey=${messageKey}`);
119
115
  void enqueue(sessionId, task).catch((err) => {
120
116
  error(`XY gateway: queue processing failed for session ${sessionId}: ${String(err)}`);
121
117
  activeMessages.delete(messageKey);
@@ -207,8 +203,8 @@ export async function monitorXYProvider(opts = {}) {
207
203
  wsManager.on("disconnected", disconnectedHandler);
208
204
  wsManager.on("error", errorHandler);
209
205
  wsManager.on("trigger-event", triggerEventHandler);
210
- // Start periodic health check (every 5 minutes)
211
- console.log("🏥 Starting periodic health check (every 5 minutes)...");
206
+ // Start periodic health check (every 6 hours)
207
+ console.log("🏥 Starting periodic health check (every 6 hours)...");
212
208
  healthCheckInterval = setInterval(() => {
213
209
  console.log("🏥 [HEALTH CHECK] Periodic WebSocket diagnostics...");
214
210
  diagnoseAllManagers();
@@ -217,7 +213,9 @@ export async function monitorXYProvider(opts = {}) {
217
213
  if (cleaned > 0) {
218
214
  console.log(`🧹 [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
219
215
  }
220
- }, 5 * 60 * 1000); // 5 minutes
216
+ // Cleanup stale temp files (older than 24 hours)
217
+ void cleanupStaleTempFiles();
218
+ }, 6 * 60 * 60 * 1000); // 6 hours
221
219
  // Connect to WebSocket servers
222
220
  wsManager.connect()
223
221
  .then(() => {
@@ -1,6 +1,5 @@
1
- import type { ChannelOnboardingAdapter } from "openclaw/plugin-sdk";
2
1
  /**
3
- * XY Channel Onboarding Adapter
4
- * Implements the ChannelOnboardingAdapter interface for OpenClaw's onboarding system
2
+ * XY Channel Onboarding Adapter (DISABLED - not currently used)
3
+ * NOTE: This is kept for future reference. Xiaoyi uses simple single-account config.
5
4
  */
6
- export declare const xyOnboardingAdapter: ChannelOnboardingAdapter;
5
+ export declare const xyOnboardingAdapter: any;
@@ -152,8 +152,8 @@ async function configure({ cfg, prompter, }) {
152
152
  };
153
153
  }
154
154
  /**
155
- * XY Channel Onboarding Adapter
156
- * Implements the ChannelOnboardingAdapter interface for OpenClaw's onboarding system
155
+ * XY Channel Onboarding Adapter (DISABLED - not currently used)
156
+ * NOTE: This is kept for future reference. Xiaoyi uses simple single-account config.
157
157
  */
158
158
  export const xyOnboardingAdapter = {
159
159
  channel,
@@ -1,6 +1,7 @@
1
- import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk";
1
+ type ChannelOutboundAdapter = any;
2
2
  /**
3
3
  * Outbound adapter for sending messages from OpenClaw to XY.
4
4
  * Uses Push service for direct message delivery.
5
5
  */
6
6
  export declare const xyOutbound: ChannelOutboundAdapter;
7
+ export {};
@@ -90,13 +90,6 @@ export const xyOutbound = {
90
90
  };
91
91
  },
92
92
  sendText: async ({ cfg, to, text, accountId }) => {
93
- // Log parameters
94
- console.log(`[xyOutbound.sendText] Called with:`, {
95
- to,
96
- accountId,
97
- textLength: text?.length || 0,
98
- textPreview: text?.slice(0, 100),
99
- });
100
93
  // Resolve configuration
101
94
  const config = resolveXYConfig(cfg);
102
95
  // Handle default push marker (for cron jobs without explicit target)
@@ -112,7 +105,7 @@ export const xyOutbound = {
112
105
  let pushDataId;
113
106
  try {
114
107
  pushDataId = await savePushData(text);
115
- console.log(`[xyOutbound.sendText] ✅ Push data saved with ID: ${pushDataId}`);
108
+ console.log(`[xyOutbound.sendText] ✅ Push data saved with ID: ${pushDataId.substring(0, 20)}`);
116
109
  }
117
110
  catch (error) {
118
111
  console.error(`[xyOutbound.sendText] ❌ Failed to save push data:`, error);
@@ -146,9 +139,8 @@ export const xyOutbound = {
146
139
  let failureCount = 0;
147
140
  for (const pushId of pushIdList) {
148
141
  try {
149
- console.log(`[xyOutbound.sendText] Sending to pushId: ${pushId.substring(0, 20)}...`);
150
- // 传入 pushDataId,使用 kind="data" 格式
151
- await pushService.sendPush(pushText, title, undefined, actualTo, pushDataId);
142
+ // 传入 pushId pushDataId,使用 kind="data" 格式
143
+ await pushService.sendPush(pushText, title, undefined, actualTo, pushDataId, pushId);
152
144
  successCount++;
153
145
  console.log(`[xyOutbound.sendText] ✅ Sent successfully to pushId: ${pushId.substring(0, 20)}...`);
154
146
  }
@@ -158,8 +150,6 @@ export const xyOutbound = {
158
150
  // 单个 pushId 发送失败不影响其他,继续处理下一个
159
151
  }
160
152
  }
161
- console.log(`[xyOutbound.sendText] 📊 Broadcast summary: ${successCount} success, ${failureCount} failures`);
162
- console.log(`[xyOutbound.sendText] Completed successfully`);
163
153
  // Return message info
164
154
  return {
165
155
  channel: "xiaoyi-channel",
@@ -168,14 +158,6 @@ export const xyOutbound = {
168
158
  };
169
159
  },
170
160
  sendMedia: async ({ cfg, to, text, mediaUrl, accountId, mediaLocalRoots }) => {
171
- // Log parameters
172
- console.log(`[xyOutbound.sendMedia] Called with:`, {
173
- to,
174
- accountId,
175
- text,
176
- mediaUrl,
177
- mediaLocalRoots,
178
- });
179
161
  // Parse to: "sessionId::taskId"
180
162
  const parts = to.split("::");
181
163
  if (parts.length !== 2) {
@@ -43,6 +43,19 @@ export declare function isTasksCancelMessage(method: string): boolean;
43
43
  * Looks for push_id in data parts under variables.systemVariables.push_id
44
44
  */
45
45
  export declare function extractPushId(parts: A2AMessagePart[]): string | null;
46
+ /**
47
+ * Extract deviceType from message parts.
48
+ * Looks for deviceType in data parts under variables.systemVariables.deviceType
49
+ * (same level as push_id).
50
+ */
51
+ export declare function extractDeviceType(parts: A2AMessagePart[]): string | null;
52
+ /**
53
+ * Extract Trigger event data from message parts.
54
+ * Looks for Trigger events with pushDataId in data parts.
55
+ */
56
+ export declare function extractTriggerData(parts: A2AMessagePart[]): {
57
+ pushDataId: string;
58
+ } | null;
46
59
  /**
47
60
  * Validate A2A request structure.
48
61
  */
@@ -72,6 +72,44 @@ export function extractPushId(parts) {
72
72
  }
73
73
  return null;
74
74
  }
75
+ /**
76
+ * Extract deviceType from message parts.
77
+ * Looks for deviceType in data parts under variables.systemVariables.deviceType
78
+ * (same level as push_id).
79
+ */
80
+ export function extractDeviceType(parts) {
81
+ for (const part of parts) {
82
+ if (part.kind === "data" && part.data) {
83
+ const deviceType = part.data.variables?.systemVariables?.deviceType;
84
+ if (deviceType && typeof deviceType === "string") {
85
+ return deviceType;
86
+ }
87
+ }
88
+ }
89
+ return null;
90
+ }
91
+ /**
92
+ * Extract Trigger event data from message parts.
93
+ * Looks for Trigger events with pushDataId in data parts.
94
+ */
95
+ export function extractTriggerData(parts) {
96
+ for (const part of parts) {
97
+ if (part.kind === "data" && part.data) {
98
+ const events = part.data.events;
99
+ if (Array.isArray(events)) {
100
+ for (const event of events) {
101
+ if (event.header?.namespace === "Common" && event.header?.name === "Trigger") {
102
+ const pushDataId = event.payload?.dataMap?.pushDataId;
103
+ if (pushDataId && typeof pushDataId === "string") {
104
+ return { pushDataId };
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ return null;
112
+ }
75
113
  /**
76
114
  * Validate A2A request structure.
77
115
  */