@ynhcj/xiaoyi-channel 1.1.26 → 1.1.28

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 (104) hide show
  1. package/dist/index.js +26 -69
  2. package/dist/src/approval-bridge.d.ts +48 -0
  3. package/dist/src/approval-bridge.js +382 -0
  4. package/dist/src/bot.js +132 -73
  5. package/dist/src/channel.js +59 -5
  6. package/dist/src/client.js +13 -23
  7. package/dist/src/cron-command.d.ts +15 -0
  8. package/dist/src/cron-command.js +49 -0
  9. package/dist/src/cron-query-handler.d.ts +7 -0
  10. package/dist/src/cron-query-handler.js +189 -0
  11. package/dist/src/cspl/call_api.d.ts +2 -0
  12. package/dist/src/cspl/call_api.js +107 -0
  13. package/dist/src/cspl/config.d.ts +4 -17
  14. package/dist/src/cspl/config.js +100 -70
  15. package/dist/src/cspl/configs.json +10 -0
  16. package/dist/src/cspl/constants.d.ts +49 -24
  17. package/dist/src/cspl/constants.js +46 -16
  18. package/dist/src/cspl/sentinel_hook.d.ts +2 -0
  19. package/dist/src/cspl/sentinel_hook.js +103 -0
  20. package/dist/src/cspl/steer-context.js +1 -1
  21. package/dist/src/cspl/upload_file.d.ts +1 -0
  22. package/dist/src/cspl/upload_file.js +211 -0
  23. package/dist/src/cspl/utils.d.ts +17 -2
  24. package/dist/src/cspl/utils.js +271 -15
  25. package/dist/src/file-upload.d.ts +5 -0
  26. package/dist/src/file-upload.js +102 -0
  27. package/dist/src/formatter.d.ts +43 -1
  28. package/dist/src/formatter.js +171 -41
  29. package/dist/src/monitor.js +64 -43
  30. package/dist/src/outbound.js +8 -9
  31. package/dist/src/parser.d.ts +8 -1
  32. package/dist/src/parser.js +71 -0
  33. package/dist/src/provider.js +51 -17
  34. package/dist/src/push.d.ts +11 -1
  35. package/dist/src/push.js +101 -17
  36. package/dist/src/reply-dispatcher.js +152 -59
  37. package/dist/src/self-evolution-handler.d.ts +1 -1
  38. package/dist/src/self-evolution-handler.js +14 -3
  39. package/dist/src/sensitive-redactor.d.ts +4 -0
  40. package/dist/src/sensitive-redactor.js +364 -0
  41. package/dist/src/task-manager.js +6 -10
  42. package/dist/src/tools/agent-as-skill-tool.d.ts +7 -0
  43. package/dist/src/tools/agent-as-skill-tool.js +190 -0
  44. package/dist/src/tools/calendar-tool.js +3 -2
  45. package/dist/src/tools/call-phone-tool.js +3 -2
  46. package/dist/src/tools/check-plugin-privilege-tool.d.ts +6 -0
  47. package/dist/src/tools/check-plugin-privilege-tool.js +182 -0
  48. package/dist/src/tools/create-alarm-tool.js +3 -2
  49. package/dist/src/tools/create-all-tools.js +11 -3
  50. package/dist/src/tools/delete-alarm-tool.js +3 -2
  51. package/dist/src/tools/device-tool-map.d.ts +1 -1
  52. package/dist/src/tools/device-tool-map.js +12 -5
  53. package/dist/src/tools/discover-cross-devices-tool.d.ts +2 -0
  54. package/dist/src/tools/discover-cross-devices-tool.js +235 -0
  55. package/dist/src/tools/display-a2ui-card-tool.d.ts +2 -0
  56. package/dist/src/tools/display-a2ui-card-tool.js +85 -0
  57. package/dist/src/tools/find-pc-devices-tool.d.ts +2 -1
  58. package/dist/src/tools/find-pc-devices-tool.js +85 -88
  59. package/dist/src/tools/get-collection-tool-schema.js +1 -1
  60. package/dist/src/tools/location-tool.js +3 -2
  61. package/dist/src/tools/modify-alarm-tool.js +3 -2
  62. package/dist/src/tools/modify-note-tool.js +3 -2
  63. package/dist/src/tools/note-tool.js +3 -2
  64. package/dist/src/tools/query-app-message-tool.js +4 -3
  65. package/dist/src/tools/query-memory-data-tool.js +4 -3
  66. package/dist/src/tools/query-todo-task-tool.js +4 -3
  67. package/dist/src/tools/save-file-to-phone-tool.js +3 -2
  68. package/dist/src/tools/save-media-to-gallery-tool.js +3 -2
  69. package/dist/src/tools/schema-tool-factory.js +1 -1
  70. package/dist/src/tools/search-alarm-tool.js +3 -2
  71. package/dist/src/tools/search-calendar-tool.js +3 -2
  72. package/dist/src/tools/search-contact-tool.js +3 -2
  73. package/dist/src/tools/search-email-tool.js +4 -3
  74. package/dist/src/tools/search-file-tool.js +8 -9
  75. package/dist/src/tools/search-message-tool.js +2 -1
  76. package/dist/src/tools/search-note-tool.js +3 -2
  77. package/dist/src/tools/search-photo-gallery-tool.js +5 -4
  78. package/dist/src/tools/send-cross-device-task-tool.d.ts +2 -0
  79. package/dist/src/tools/send-cross-device-task-tool.js +299 -0
  80. package/dist/src/tools/send-email-tool.js +4 -3
  81. package/dist/src/tools/send-file-to-user-tool.d.ts +1 -1
  82. package/dist/src/tools/send-file-to-user-tool.js +37 -8
  83. package/dist/src/tools/send-html-card-tool.d.ts +7 -0
  84. package/dist/src/tools/send-html-card-tool.js +113 -0
  85. package/dist/src/tools/send-message-tool.js +2 -1
  86. package/dist/src/tools/session-manager.d.ts +17 -1
  87. package/dist/src/tools/session-manager.js +87 -1
  88. package/dist/src/tools/upload-file-tool.js +9 -7
  89. package/dist/src/tools/upload-photo-tool.js +5 -4
  90. package/dist/src/tools/xiaoyi-add-collection-tool.js +5 -3
  91. package/dist/src/tools/xiaoyi-collection-tool.js +4 -3
  92. package/dist/src/tools/xiaoyi-delete-collection-tool.js +4 -3
  93. package/dist/src/tools/xiaoyi-gui-tool.js +8 -2
  94. package/dist/src/trigger-handler.js +4 -7
  95. package/dist/src/types.d.ts +25 -1
  96. package/dist/src/utils/config-manager.js +3 -6
  97. package/dist/src/utils/logger.d.ts +8 -0
  98. package/dist/src/utils/logger.js +69 -34
  99. package/dist/src/utils/pushdata-manager.js +1 -5
  100. package/dist/src/utils/pushid-manager.js +1 -2
  101. package/dist/src/utils/runtime-manager.js +1 -4
  102. package/dist/src/websocket.d.ts +3 -0
  103. package/dist/src/websocket.js +242 -38
  104. package/package.json +1 -1
@@ -17,6 +17,7 @@ export interface SendA2AResponseParams {
17
17
  }>;
18
18
  errorCode?: number | string;
19
19
  errorMessage?: string;
20
+ log?: boolean;
20
21
  }
21
22
  /**
22
23
  * Send an A2A artifact update response.
@@ -63,12 +64,53 @@ export interface SendCommandParams {
63
64
  sessionId: string;
64
65
  taskId: string;
65
66
  messageId: string;
66
- command: A2ACommand;
67
+ command?: A2ACommand;
68
+ commands?: A2ACommand[];
69
+ /** toolCallId from the tool's execute() — used for cron detection via hook-set Map. */
70
+ toolCallId?: string;
71
+ /** When true, the artifact-update is sent with final=true. Default: false. */
72
+ final?: boolean;
67
73
  }
68
74
  /**
69
75
  * Send a command as an artifact update (final=false).
76
+ *
77
+ * Cron-aware: if the sessionId starts with the cron prefix ("cron-"),
78
+ * the command is delivered through the push channel instead of the
79
+ * WebSocket session, because cron-triggered tool calls have no active
80
+ * WebSocket session. The device receives the push, executes the command,
81
+ * and returns results through the normal WebSocket path — so response
82
+ * listening in the calling tool works unchanged.
70
83
  */
71
84
  export declare function sendCommand(params: SendCommandParams): Promise<void>;
85
+ /**
86
+ * Parameters for sending a card (e.g., HTML H5 card).
87
+ */
88
+ export interface SendCardParams {
89
+ config: XYChannelConfig;
90
+ sessionId: string;
91
+ taskId: string;
92
+ messageId: string;
93
+ /** toolCallId from the tool's execute() — used for cron detection via hook-set Map. */
94
+ toolCallId?: string;
95
+ /** When true, the artifact-update is sent with final=true. Default: false. */
96
+ final?: boolean;
97
+ /** Array of card data objects to send. */
98
+ cardsInfo: CardDataObject[];
99
+ }
100
+ /**
101
+ * Card data object for sending display cards.
102
+ */
103
+ export interface CardDataObject {
104
+ cardName: string;
105
+ cardData: Record<string, any>;
106
+ displayType: string;
107
+ }
108
+ /**
109
+ * Send a card (e.g., HTML H5 card) as an artifact update (final=false).
110
+ *
111
+ * Cron-aware: same routing logic as sendCommand.
112
+ */
113
+ export declare function sendCard(params: SendCardParams): Promise<void>;
72
114
  /**
73
115
  * Parameters for sending a clearContext response.
74
116
  */
@@ -3,11 +3,49 @@ import { v4 as uuidv4 } from "uuid";
3
3
  import { getXYWebSocketManager } from "./client.js";
4
4
  import { logger } from "./utils/logger.js";
5
5
  import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
6
+ import { redactSensitiveText, containsSensitiveInfo } from "./sensitive-redactor.js";
7
+ import { rewriteOutboundApprovalText } from "./approval-bridge.js";
8
+ import { isCronToolCall } from "./tools/session-manager.js";
9
+ // ─────────────────────────────────────────────────────────────
10
+ // 敏感信息脱敏辅助函数
11
+ // ─────────────────────────────────────────────────────────────
12
+ const MESSAGE_CONTENT_KEYS = new Set(["text", "reasoningText", "content", "message"]);
13
+ function redactMessagePayload(value, currentKey) {
14
+ if (value === null || value === undefined) {
15
+ return value;
16
+ }
17
+ if (typeof value === "string") {
18
+ if (currentKey === undefined || MESSAGE_CONTENT_KEYS.has(currentKey)) {
19
+ return redactSensitiveText(value);
20
+ }
21
+ return value;
22
+ }
23
+ if (Array.isArray(value)) {
24
+ return value.map(item => redactMessagePayload(item, currentKey));
25
+ }
26
+ if (typeof value === "object") {
27
+ const result = {};
28
+ for (const key of Object.keys(value)) {
29
+ result[key] = redactMessagePayload(value[key], key);
30
+ }
31
+ return result;
32
+ }
33
+ return value;
34
+ }
35
+ function buildTextPreview(text) {
36
+ if (typeof text !== "string" || text.length === 0) {
37
+ return "";
38
+ }
39
+ return text.length <= 10 ? text : `${text.slice(0, 5)}***${text.slice(-5)}`;
40
+ }
6
41
  /**
7
42
  * Send an A2A artifact update response.
8
43
  */
9
44
  export async function sendA2AResponse(params) {
10
- const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage } = params;
45
+ const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage, log: shouldLog = true } = params;
46
+ const log = logger.withContext(sessionId, taskId);
47
+ // 审批桥接:将 OpenClaw 的审批提示翻译成用户友好的确认文案
48
+ const bridgedText = text === undefined ? text : rewriteOutboundApprovalText(sessionId, text);
11
49
  // Build artifact update event
12
50
  const artifact = {
13
51
  taskId,
@@ -21,10 +59,10 @@ export async function sendA2AResponse(params) {
21
59
  },
22
60
  };
23
61
  // Add text part (even if empty string, to maintain parts structure)
24
- if (text !== undefined) {
62
+ if (bridgedText !== undefined) {
25
63
  artifact.artifact.parts.push({
26
64
  kind: "text",
27
- text,
65
+ text: bridgedText,
28
66
  });
29
67
  }
30
68
  // Add file parts if provided
@@ -34,6 +72,8 @@ export async function sendA2AResponse(params) {
34
72
  data: { fileInfo: files },
35
73
  });
36
74
  }
75
+ // 对消息内容字段做敏感信息脱敏,不修改协议层的 id 等字段
76
+ artifact.artifact.parts = redactMessagePayload(artifact.artifact.parts, "parts");
37
77
  // Build JSON-RPC response
38
78
  const jsonRpcResponse = {
39
79
  jsonrpc: "2.0",
@@ -46,7 +86,7 @@ export async function sendA2AResponse(params) {
46
86
  code: errorCode,
47
87
  message: errorMessage ?? "任务执行异常,请重试",
48
88
  };
49
- logger.log(`[A2A_RESPONSE] ⚠️ Including error code: ${errorCode}`);
89
+ log.log(`[A2A_RESPONSE] Including error code: ${errorCode}`);
50
90
  }
51
91
  // Send via WebSocket
52
92
  const wsManager = getXYWebSocketManager(config);
@@ -57,14 +97,14 @@ export async function sendA2AResponse(params) {
57
97
  taskId,
58
98
  msgDetail: JSON.stringify(jsonRpcResponse),
59
99
  };
60
- // 📋 Log complete response body
61
- logger.log(`[A2A_RESPONSE] 📤 Sending A2A artifact-update response: taskId: ${taskId}`);
62
- logger.log(`[A2A_RESPONSE] - append: ${append}`);
63
- logger.log(`[A2A_RESPONSE] - final: ${final}`);
64
- logger.log(`[A2A_RESPONSE] - text: ${text.length <= 10 ? text : text.slice(0, 5) + '***' + text.slice(-5)}`);
65
- logger.log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
100
+ if (shouldLog) {
101
+ const redactedText = redactSensitiveText(bridgedText ?? "");
102
+ log.log(`[A2A_RESPONSE] Sending artifact-update, append=${append}, final=${final}, text=${buildTextPreview(redactedText)}, files=${files?.length ?? 0}, sensitive=${containsSensitiveInfo(bridgedText ?? "")}`);
103
+ }
66
104
  await wsManager.sendMessage(sessionId, outboundMessage);
67
- logger.log(`[A2A_RESPONSE] ✅ Message sent successfully`);
105
+ if (shouldLog) {
106
+ log.log(`[A2A_RESPONSE] Message sent successfully`);
107
+ }
68
108
  }
69
109
  /**
70
110
  * Send an A2A artifact-update with reasoningText part.
@@ -73,6 +113,9 @@ export async function sendA2AResponse(params) {
73
113
  */
74
114
  export async function sendReasoningTextUpdate(params) {
75
115
  const { config, sessionId, taskId, messageId, text, append = true } = params;
116
+ const log = logger.withContext(sessionId, taskId);
117
+ // 审批桥接
118
+ const bridgedText = rewriteOutboundApprovalText(sessionId, text);
76
119
  const artifact = {
77
120
  taskId,
78
121
  kind: "artifact-update",
@@ -84,11 +127,13 @@ export async function sendReasoningTextUpdate(params) {
84
127
  parts: [
85
128
  {
86
129
  kind: "reasoningText",
87
- reasoningText: text,
130
+ reasoningText: bridgedText,
88
131
  },
89
132
  ],
90
133
  },
91
134
  };
135
+ // 对消息内容字段做敏感信息脱敏
136
+ artifact.artifact.parts = redactMessagePayload(artifact.artifact.parts, "parts");
92
137
  const jsonRpcResponse = {
93
138
  jsonrpc: "2.0",
94
139
  id: messageId,
@@ -114,21 +159,26 @@ export async function sendStatusUpdate(params) {
114
159
  // fall back to closure-captured values
115
160
  const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
116
161
  const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
162
+ const log = logger.withContext(sessionId, currentTaskId);
163
+ // 审批桥接和脱敏
164
+ const bridgedText = rewriteOutboundApprovalText(sessionId, text);
165
+ const redactedText = redactSensitiveText(bridgedText);
117
166
  // Build status update event following A2A protocol standard
167
+ const statusMessage = redactMessagePayload({
168
+ role: "agent",
169
+ parts: [
170
+ {
171
+ kind: "text",
172
+ text: bridgedText,
173
+ },
174
+ ],
175
+ });
118
176
  const statusUpdate = {
119
177
  taskId: currentTaskId,
120
178
  kind: "status-update",
121
179
  final: false, // Status updates should not end the stream
122
180
  status: {
123
- message: {
124
- role: "agent",
125
- parts: [
126
- {
127
- kind: "text",
128
- text,
129
- },
130
- ],
131
- },
181
+ message: statusMessage,
132
182
  state,
133
183
  },
134
184
  };
@@ -147,21 +197,38 @@ export async function sendStatusUpdate(params) {
147
197
  taskId: currentTaskId,
148
198
  msgDetail: JSON.stringify(jsonRpcResponse),
149
199
  };
150
- // 📋 Log complete response body
151
- logger.log(`[A2A_STATUS] 📤 Sending A2A status-update:`);
152
- logger.log(`[A2A_STATUS] - taskId: ${currentTaskId}`);
153
- logger.log(`[A2A_STATUS] - text: "${text}"`);
200
+ // Log complete response body
201
+ log.log(`[A2A_STATUS] Sending status-update, text="${redactedText}"`);
154
202
  await wsManager.sendMessage(sessionId, outboundMessage);
155
203
  }
156
204
  /**
157
205
  * Send a command as an artifact update (final=false).
206
+ *
207
+ * Cron-aware: if the sessionId starts with the cron prefix ("cron-"),
208
+ * the command is delivered through the push channel instead of the
209
+ * WebSocket session, because cron-triggered tool calls have no active
210
+ * WebSocket session. The device receives the push, executes the command,
211
+ * and returns results through the normal WebSocket path — so response
212
+ * listening in the calling tool works unchanged.
158
213
  */
159
214
  export async function sendCommand(params) {
160
- const { config, sessionId, taskId, messageId, command } = params;
215
+ const { config, sessionId, taskId, messageId, toolCallId } = params;
216
+ const commands = params.commands ?? (params.command ? [params.command] : []);
217
+ if (commands.length === 0) {
218
+ throw new Error("sendCommand requires command or commands.");
219
+ }
220
+ // ── Cron mode: disabled ────────────────────────────────────────
221
+ // sendCommandViaPush is disabled in this version. Cron-triggered
222
+ // tool calls that try to send commands will be rejected.
223
+ if (sessionId.startsWith("cron-") || isCronToolCall(toolCallId)) {
224
+ throw new Error("sendCommandViaPush is disabled in this version");
225
+ }
226
+ // ── Normal mode: WebSocket ─────────────────────────────────────
161
227
  // Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
162
228
  // fall back to closure-captured values
163
229
  const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
164
230
  const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
231
+ const log = logger.withContext(sessionId, currentTaskId);
165
232
  // Build artifact update with command as data
166
233
  // Wrap command in commands array as per protocol requirement
167
234
  const artifact = {
@@ -169,19 +236,21 @@ export async function sendCommand(params) {
169
236
  kind: "artifact-update",
170
237
  append: false,
171
238
  lastChunk: true,
172
- final: false, // Commands are not final
239
+ final: params.final ?? false,
173
240
  artifact: {
174
241
  artifactId: uuidv4(),
175
242
  parts: [
176
243
  {
177
244
  kind: "data",
178
245
  data: {
179
- commands: [command],
246
+ commands,
180
247
  },
181
248
  },
182
249
  ],
183
250
  },
184
251
  };
252
+ // 对消息内容字段做敏感信息脱敏
253
+ artifact.artifact.parts = redactMessagePayload(artifact.artifact.parts, "parts");
185
254
  // Build JSON-RPC response
186
255
  const jsonRpcResponse = {
187
256
  jsonrpc: "2.0",
@@ -197,16 +266,70 @@ export async function sendCommand(params) {
197
266
  taskId: currentTaskId,
198
267
  msgDetail: JSON.stringify(jsonRpcResponse),
199
268
  };
200
- // 📋 Log complete response body
201
- logger.log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${currentTaskId}`);
269
+ // Log complete response body
270
+ log.log(`[A2A_COMMAND] Sending command`);
202
271
  await wsManager.sendMessage(sessionId, outboundMessage);
203
- logger.log(`[A2A_COMMAND] Command sent successfully`);
272
+ log.log(`[A2A_COMMAND] Command sent successfully`);
273
+ }
274
+ /**
275
+ * Send a card (e.g., HTML H5 card) as an artifact update (final=false).
276
+ *
277
+ * Cron-aware: same routing logic as sendCommand.
278
+ */
279
+ export async function sendCard(params) {
280
+ const { config, sessionId, taskId, messageId, toolCallId } = params;
281
+ // ── Cron mode: route through push channel ──────────────────────
282
+ if (sessionId.startsWith("cron-") || isCronToolCall(toolCallId)) {
283
+ throw new Error("sendCard does not support cron mode");
284
+ }
285
+ // ── Normal mode: WebSocket ─────────────────────────────────────
286
+ const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
287
+ const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
288
+ const log = logger.withContext(sessionId, currentTaskId);
289
+ // Build artifact update with cardsInfo as data
290
+ const artifact = {
291
+ taskId: currentTaskId,
292
+ kind: "artifact-update",
293
+ append: false,
294
+ lastChunk: true,
295
+ final: params.final ?? false,
296
+ artifact: {
297
+ artifactId: uuidv4(),
298
+ parts: [
299
+ {
300
+ kind: "data",
301
+ data: {
302
+ cardsInfo: params.cardsInfo,
303
+ },
304
+ },
305
+ ],
306
+ },
307
+ };
308
+ // Build JSON-RPC response
309
+ const jsonRpcResponse = {
310
+ jsonrpc: "2.0",
311
+ id: currentMessageId,
312
+ result: artifact,
313
+ };
314
+ // Send via WebSocket
315
+ const wsManager = getXYWebSocketManager(config);
316
+ const outboundMessage = {
317
+ msgType: "agent_response",
318
+ agentId: config.agentId,
319
+ sessionId,
320
+ taskId: currentTaskId,
321
+ msgDetail: JSON.stringify(jsonRpcResponse),
322
+ };
323
+ log.log(`[A2A_CARD] Sending card`);
324
+ await wsManager.sendMessage(sessionId, outboundMessage);
325
+ log.log(`[A2A_CARD] Card sent successfully`);
204
326
  }
205
327
  /**
206
328
  * Send a clearContext response.
207
329
  */
208
330
  export async function sendClearContextResponse(params) {
209
331
  const { config, sessionId, messageId } = params;
332
+ const log = logger.withContext(sessionId, "");
210
333
  // Build JSON-RPC response for clearContext
211
334
  const jsonRpcResponse = {
212
335
  jsonrpc: "2.0",
@@ -232,13 +355,14 @@ export async function sendClearContextResponse(params) {
232
355
  msgDetail: JSON.stringify(jsonRpcResponse),
233
356
  };
234
357
  await wsManager.sendMessage(sessionId, outboundMessage);
235
- logger.log(`Sent clearContext response: sessionId=${sessionId}`);
358
+ log.log(`[CLEAR_CONTEXT] Sent clearContext response`);
236
359
  }
237
360
  /**
238
361
  * Send a tasks/cancel response.
239
362
  */
240
363
  export async function sendTasksCancelResponse(params) {
241
364
  const { config, sessionId, taskId, messageId } = params;
365
+ const log = logger.withContext(sessionId, taskId);
242
366
  // Build JSON-RPC response for tasks/cancel
243
367
  // Note: Using any to bypass type check as the response format differs from standard A2A types
244
368
  const jsonRpcResponse = {
@@ -265,13 +389,24 @@ export async function sendTasksCancelResponse(params) {
265
389
  msgDetail: JSON.stringify(jsonRpcResponse),
266
390
  };
267
391
  await wsManager.sendMessage(sessionId, outboundMessage);
268
- logger.log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
392
+ log.log(`[TASKS_CANCEL] Sent tasks/cancel response`);
269
393
  }
270
394
  /**
271
395
  * Send a Trigger response with pushData content.
272
396
  */
273
397
  export async function sendTriggerResponse(params) {
274
398
  const { config, sessionId, taskId, messageId, content } = params;
399
+ const log = logger.withContext(sessionId, taskId);
400
+ // 审批桥接和脱敏
401
+ const bridgedContent = rewriteOutboundApprovalText(sessionId, content);
402
+ const redactedContent = redactSensitiveText(bridgedContent);
403
+ // 对消息内容做敏感信息脱敏
404
+ const artifactParts = redactMessagePayload([
405
+ {
406
+ kind: "text",
407
+ text: bridgedContent,
408
+ },
409
+ ], "parts");
275
410
  // Build JSON-RPC response for Trigger
276
411
  const jsonRpcResponse = {
277
412
  jsonrpc: "2.0",
@@ -284,12 +419,7 @@ export async function sendTriggerResponse(params) {
284
419
  final: true,
285
420
  artifact: {
286
421
  artifactId: uuidv4(),
287
- parts: [
288
- {
289
- kind: "text",
290
- text: content,
291
- },
292
- ],
422
+ parts: artifactParts,
293
423
  },
294
424
  },
295
425
  error: {
@@ -306,7 +436,7 @@ export async function sendTriggerResponse(params) {
306
436
  taskId,
307
437
  msgDetail: JSON.stringify(jsonRpcResponse),
308
438
  };
309
- logger.log(`[TRIGGER_RESPONSE] Sending Trigger response: sessionId=${sessionId}, taskId=${taskId}`);
439
+ log.log(`[TRIGGER_RESPONSE] Sending Trigger response, text=${buildTextPreview(redactedContent)}`);
310
440
  await wsManager.sendMessage(sessionId, outboundMessage);
311
- logger.log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
441
+ log.log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
312
442
  }
@@ -7,6 +7,7 @@ import { sendA2AResponse } from "./formatter.js";
7
7
  import { handleTriggerEvent } from "./trigger-handler.js";
8
8
  import { handleSelfEvolutionEvent, handleSelfEvolutionStateGetEvent } from "./self-evolution-handler.js";
9
9
  import { handleLoginTokenEvent } from "./login-token-handler.js";
10
+ import { handleCronQueryEvent } from "./cron-query-handler.js";
10
11
  import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
11
12
  import { cleanupStaleSessions, getActiveSessionCount, cleanupAllSessions } from "./tools/session-manager.js";
12
13
  import { logger } from "./utils/logger.js";
@@ -51,7 +52,7 @@ export async function monitorXYProvider(opts = {}) {
51
52
  }
52
53
  : undefined;
53
54
  // 🔍 Diagnose WebSocket managers before gateway start
54
- logger.log("🔍 [DIAGNOSTICS] Checking WebSocket managers before gateway start...");
55
+ logger.log("[DIAGNOSTICS] Checking WebSocket managers before gateway start...");
55
56
  diagnoseAllManagers();
56
57
  // Get WebSocket manager (cached)
57
58
  const wsManager = getXYWebSocketManager(account, runtime);
@@ -77,12 +78,12 @@ export async function monitorXYProvider(opts = {}) {
77
78
  // Event handlers (defined early so they can be referenced in cleanup)
78
79
  const messageHandler = (message, sessionId, serverId) => {
79
80
  const messageKey = `${sessionId}::${message.id}`;
80
- logger.log(`[MONITOR-HANDLER] ####### messageHandler triggered: sessionId=${sessionId}, messageId=${message.id} #######`);
81
+ logger.log(`[MONITOR-HANDLER] messageHandler triggered: messageId=${message.id}`);
81
82
  // ✅ Report health: received a message
82
83
  trackEvent?.();
83
84
  // Check for duplicate message handling
84
85
  if (activeMessages.has(messageKey)) {
85
- logger.error(`[MONITOR-HANDLER] ⚠️ WARNING: Duplicate message detected! messageKey=${messageKey}, this may cause duplicate dispatchers!`);
86
+ logger.error(`[MONITOR-HANDLER] WARNING: Duplicate message detected, messageKey=${messageKey}`);
86
87
  }
87
88
  activeMessages.add(messageKey);
88
89
  const task = async () => {
@@ -103,45 +104,57 @@ export async function monitorXYProvider(opts = {}) {
103
104
  }
104
105
  catch (err) {
105
106
  // ✅ Only log error, don't re-throw to prevent gateway restart
106
- releaseGate();
107
107
  logger.error(`XY gateway: error handling message from ${serverId}: ${String(err)}`);
108
108
  }
109
109
  finally {
110
+ // 🔑 确保门控始终被释放。handleXYMessage 内部会 catch 所有异常
111
+ // 且某些提前返回路径(clearContext、tasks/cancel 等)不会调用
112
+ // onInitComplete,因此必须在 finally 中兜底释放。
113
+ releaseGate();
110
114
  // Remove from active messages when done
111
115
  activeMessages.delete(messageKey);
112
116
  }
113
117
  };
114
118
  // 🔑 核心改造:检测steer模式
115
- // 需要提前解析消息以获取sessionId
116
- try {
117
- const parsed = parseA2AMessage(message);
118
- const steerMode = cfg.messages?.queue?.mode === "steer";
119
- const hasActiveRun = hasActiveTask(parsed.sessionId);
120
- if (steerMode && hasActiveRun) {
121
- // Steer模式且有活跃任务:不入队列,直接并发执行
122
- logger.log(`[MONITOR-HANDLER] 🔄 STEER MODE: Executing concurrently for messageKey=${messageKey}`);
123
- logger.log(`[MONITOR-HANDLER] - sessionId: ${parsed.sessionId}`);
124
- void task().catch((err) => {
125
- logger.error(`XY gateway: concurrent steer task failed for ${messageKey}: ${String(err)}`);
126
- activeMessages.delete(messageKey);
127
- });
119
+ // clearContext / tasks/cancel 没有 params,跳过 parseA2AMessage 直接入队
120
+ const messageMethod = message.method;
121
+ if (messageMethod === "clearContext" || messageMethod === "clear_context"
122
+ || messageMethod === "tasks/cancel" || messageMethod === "tasks_cancel") {
123
+ void enqueue(sessionId, task).catch((err) => {
124
+ logger.error(`XY gateway: queue processing failed: ${String(err)}`);
125
+ activeMessages.delete(messageKey);
126
+ });
127
+ }
128
+ else {
129
+ try {
130
+ const parsed = parseA2AMessage(message);
131
+ const steerMode = cfg.messages?.queue?.mode === "steer";
132
+ const hasActiveRun = hasActiveTask(parsed.sessionId);
133
+ if (steerMode && hasActiveRun) {
134
+ // Steer模式且有活跃任务:不入队列,直接并发执行
135
+ logger.log(`[MONITOR-HANDLER] STEER MODE: Executing concurrently for messageKey=${messageKey}`);
136
+ void task().catch((err) => {
137
+ logger.error(`XY gateway: concurrent steer task failed for ${messageKey}: ${String(err)}`);
138
+ activeMessages.delete(messageKey);
139
+ });
140
+ }
141
+ else {
142
+ // 正常模式:入队列串行执行
143
+ void enqueue(sessionId, task).catch((err) => {
144
+ logger.error(`XY gateway: queue processing failed: ${String(err)}`);
145
+ activeMessages.delete(messageKey);
146
+ });
147
+ }
128
148
  }
129
- else {
130
- // 正常模式:入队列串行执行
149
+ catch (parseErr) {
150
+ // 解析失败,回退到正常队列模式
151
+ logger.error(`[MONITOR-HANDLER] Failed to parse message for steer detection: ${String(parseErr)}`);
131
152
  void enqueue(sessionId, task).catch((err) => {
132
- logger.error(`XY gateway: queue processing failed for session ${sessionId}: ${String(err)}`);
153
+ logger.error(`XY gateway: queue processing failed: ${String(err)}`);
133
154
  activeMessages.delete(messageKey);
134
155
  });
135
156
  }
136
157
  }
137
- catch (parseErr) {
138
- // 解析失败,回退到正常队列模式
139
- logger.error(`[MONITOR-HANDLER] Failed to parse message for steer detection: ${String(parseErr)}`);
140
- void enqueue(sessionId, task).catch((err) => {
141
- logger.error(`XY gateway: queue processing failed for session ${sessionId}: ${String(err)}`);
142
- activeMessages.delete(messageKey);
143
- });
144
- }
145
158
  };
146
159
  const connectedHandler = (serverId) => {
147
160
  if (!loggedServers.has(serverId)) {
@@ -164,9 +177,7 @@ export async function monitorXYProvider(opts = {}) {
164
177
  logger.error(`XY gateway: ${serverId} error: ${String(err)}`);
165
178
  };
166
179
  const triggerEventHandler = (context) => {
167
- logger.log(`[MONITOR] 📌 Received trigger-event, dispatching to handler...`);
168
- logger.log(`[MONITOR] - sessionId: ${context.sessionId}`);
169
- logger.log(`[MONITOR] - taskId: ${context.taskId}`);
180
+ logger.log(`[MONITOR] Received trigger-event, dispatching to handler...`);
170
181
  // 异步处理 Trigger 事件,不阻塞主流程
171
182
  handleTriggerEvent(context, cfg, runtime, accountId).catch((err) => {
172
183
  logger.error(`[MONITOR] Failed to handle trigger-event:`, err);
@@ -174,7 +185,9 @@ export async function monitorXYProvider(opts = {}) {
174
185
  };
175
186
  const selfEvolutionHandler = (context) => {
176
187
  logger.log(`[MONITOR] Received self-evolution-event, dispatching to handler...`);
177
- handleSelfEvolutionEvent(context, runtime);
188
+ handleSelfEvolutionEvent(context, cfg).catch((err) => {
189
+ logger.error(`[MONITOR] Failed to handle self-evolution-event:`, err);
190
+ });
178
191
  };
179
192
  const selfEvolutionStateGetHandler = (context) => {
180
193
  logger.log(`[MONITOR] Received self-evolution-state-get-event, dispatching to handler...`);
@@ -186,16 +199,22 @@ export async function monitorXYProvider(opts = {}) {
186
199
  logger.log(`[MONITOR] Received login-token-event, dispatching to handler...`);
187
200
  handleLoginTokenEvent(context, runtime);
188
201
  };
202
+ const cronQueryEventHandler = (context) => {
203
+ logger.log(`[MONITOR] Received cron-query-event, dispatching to handler...`);
204
+ handleCronQueryEvent(context, cfg).catch((err) => {
205
+ logger.error(`[MONITOR] Failed to handle cron-query-event:`, err);
206
+ });
207
+ };
189
208
  const cleanup = () => {
190
209
  logger.log("XY gateway: cleaning up...");
191
210
  // 🔍 Diagnose before cleanup
192
- logger.log("🔍 [DIAGNOSTICS] Checking WebSocket managers before cleanup...");
211
+ logger.log("[DIAGNOSTICS] Checking WebSocket managers before cleanup...");
193
212
  diagnoseAllManagers();
194
213
  // Stop health check interval
195
214
  if (healthCheckInterval) {
196
215
  clearInterval(healthCheckInterval);
197
216
  healthCheckInterval = null;
198
- logger.log("⏸️ Stopped periodic health check");
217
+ logger.log("Stopped periodic health check");
199
218
  }
200
219
  // Remove event handlers to prevent duplicate calls on gateway restart
201
220
  wsManager.off("message", messageHandler);
@@ -206,6 +225,7 @@ export async function monitorXYProvider(opts = {}) {
206
225
  wsManager.off("self-evolution-event", selfEvolutionHandler);
207
226
  wsManager.off("self-evolution-state-get-event", selfEvolutionStateGetHandler);
208
227
  wsManager.off("login-token-event", loginTokenEventHandler);
228
+ wsManager.off("cron-query-event", cronQueryEventHandler);
209
229
  // ✅ Disconnect the wsManager to prevent connection leaks
210
230
  // This is safe because each gateway lifecycle should have clean connections
211
231
  wsManager.disconnect();
@@ -215,9 +235,9 @@ export async function monitorXYProvider(opts = {}) {
215
235
  cleanupAllSessions();
216
236
  loggedServers.clear();
217
237
  activeMessages.clear();
218
- logger.log(`[MONITOR-HANDLER] 🧹 Cleanup complete, cleared active messages and sessions`);
238
+ logger.log(`[MONITOR-HANDLER] Cleanup complete, cleared active messages and sessions`);
219
239
  // 🔍 Diagnose after cleanup
220
- logger.log("🔍 [DIAGNOSTICS] Checking WebSocket managers after cleanup...");
240
+ logger.log("[DIAGNOSTICS] Checking WebSocket managers after cleanup...");
221
241
  diagnoseAllManagers();
222
242
  };
223
243
  const handleAbort = async () => {
@@ -228,7 +248,7 @@ export async function monitorXYProvider(opts = {}) {
228
248
  if (activeBindings.length > 0) {
229
249
  const config = resolveXYConfig(cfg);
230
250
  const notificationText = "Gateway即将重启,重启期间可能短暂出现\u201c环境异常\u201d提示,请稍候并耐心重试~";
231
- logger.log(`[MONITOR] 📤 Sending restart notifications to ${activeBindings.length} active session(s)`);
251
+ logger.log(`[MONITOR] Sending restart notifications to ${activeBindings.length} active session(s)`);
232
252
  const sendPromises = activeBindings.map(binding => sendA2AResponse({
233
253
  config,
234
254
  sessionId: binding.sessionId,
@@ -238,10 +258,10 @@ export async function monitorXYProvider(opts = {}) {
238
258
  append: false,
239
259
  final: true,
240
260
  }).catch(err => {
241
- logger.error(`[MONITOR] Failed to send restart notification to session ${binding.sessionId}: ${String(err)}`);
261
+ logger.error(`[MONITOR] Failed to send restart notification: ${String(err)}`);
242
262
  }));
243
263
  await Promise.all(sendPromises);
244
- logger.log(`[MONITOR] Restart notifications sent to ${activeBindings.length} session(s)`);
264
+ logger.log(`[MONITOR] Restart notifications sent to ${activeBindings.length} session(s)`);
245
265
  }
246
266
  else {
247
267
  logger.log(`[MONITOR] No active sessions, skipping restart notifications`);
@@ -269,21 +289,22 @@ export async function monitorXYProvider(opts = {}) {
269
289
  wsManager.on("self-evolution-event", selfEvolutionHandler);
270
290
  wsManager.on("self-evolution-state-get-event", selfEvolutionStateGetHandler);
271
291
  wsManager.on("login-token-event", loginTokenEventHandler);
292
+ wsManager.on("cron-query-event", cronQueryEventHandler);
272
293
  // Start periodic health check (every 6 hours)
273
- logger.log("🏥 Starting periodic health check (every 6 hours)...");
294
+ logger.log("Starting periodic health check (every 6 hours)...");
274
295
  healthCheckInterval = setInterval(() => {
275
- logger.log("🏥 [HEALTH CHECK] Periodic WebSocket diagnostics...");
296
+ logger.log("[HEALTH CHECK] Periodic WebSocket diagnostics...");
276
297
  diagnoseAllManagers();
277
298
  // Auto-cleanup orphan connections
278
299
  const cleaned = cleanupOrphanConnections();
279
300
  if (cleaned > 0) {
280
- logger.log(`🧹 [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
301
+ logger.log(`[HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
281
302
  }
282
303
  // Cleanup stale sessions (older than 10min TTL)
283
304
  const cleanedSessions = cleanupStaleSessions();
284
305
  const remainingSessions = getActiveSessionCount();
285
306
  if (cleanedSessions > 0 || remainingSessions > 0) {
286
- logger.log(`🧹 [HEALTH CHECK] Sessions: cleaned=${cleanedSessions}, active=${remainingSessions}`);
307
+ logger.log(`[HEALTH CHECK] Sessions: cleaned=${cleanedSessions}, active=${remainingSessions}`);
287
308
  }
288
309
  // Cleanup stale temp files (older than 24 hours)
289
310
  void cleanupStaleTempFiles();