@ynhcj/xiaoyi-channel 0.0.150-beta → 0.0.151-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/index.js +3 -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 +64 -68
  5. package/dist/src/client.js +13 -23
  6. package/dist/src/cspl/call_api.d.ts +2 -0
  7. package/dist/src/cspl/call_api.js +107 -0
  8. package/dist/src/cspl/config.d.ts +4 -17
  9. package/dist/src/cspl/config.js +80 -70
  10. package/dist/src/cspl/configs.json +10 -0
  11. package/dist/src/cspl/constants.d.ts +46 -24
  12. package/dist/src/cspl/constants.js +41 -16
  13. package/dist/src/cspl/sentinel_hook.d.ts +2 -0
  14. package/dist/src/cspl/sentinel_hook.js +84 -0
  15. package/dist/src/cspl/steer-context.js +1 -1
  16. package/dist/src/cspl/upload_file.d.ts +1 -0
  17. package/dist/src/cspl/upload_file.js +211 -0
  18. package/dist/src/cspl/utils.d.ts +11 -2
  19. package/dist/src/cspl/utils.js +265 -15
  20. package/dist/src/formatter.js +92 -37
  21. package/dist/src/monitor.js +18 -21
  22. package/dist/src/outbound.js +8 -9
  23. package/dist/src/push.js +8 -15
  24. package/dist/src/reply-dispatcher.js +39 -48
  25. package/dist/src/self-evolution-handler.js +1 -1
  26. package/dist/src/sensitive-redactor.d.ts +4 -0
  27. package/dist/src/sensitive-redactor.js +364 -0
  28. package/dist/src/task-manager.js +6 -10
  29. package/dist/src/tools/agent-as-skill-tool.d.ts +7 -0
  30. package/dist/src/tools/agent-as-skill-tool.js +138 -0
  31. package/dist/src/tools/calendar-tool.js +1 -1
  32. package/dist/src/tools/call-device-tool.js +3 -0
  33. package/dist/src/tools/call-phone-tool.js +1 -1
  34. package/dist/src/tools/create-alarm-tool.js +1 -1
  35. package/dist/src/tools/create-all-tools.js +5 -1
  36. package/dist/src/tools/delete-alarm-tool.js +1 -1
  37. package/dist/src/tools/find-pc-devices-tool.d.ts +2 -1
  38. package/dist/src/tools/find-pc-devices-tool.js +84 -88
  39. package/dist/src/tools/get-device-file-tool-schema.js +3 -2
  40. package/dist/src/tools/location-tool.js +1 -1
  41. package/dist/src/tools/modify-alarm-tool.js +1 -1
  42. package/dist/src/tools/modify-note-tool.js +1 -1
  43. package/dist/src/tools/note-tool.js +1 -1
  44. package/dist/src/tools/query-app-message-tool.js +1 -1
  45. package/dist/src/tools/query-memory-data-tool.js +1 -1
  46. package/dist/src/tools/query-todo-task-tool.js +1 -1
  47. package/dist/src/tools/save-file-to-phone-tool.js +1 -1
  48. package/dist/src/tools/save-media-to-gallery-tool.js +1 -1
  49. package/dist/src/tools/search-alarm-tool.js +1 -1
  50. package/dist/src/tools/search-calendar-tool.js +1 -1
  51. package/dist/src/tools/search-contact-tool.js +1 -1
  52. package/dist/src/tools/search-email-tool.js +1 -1
  53. package/dist/src/tools/search-file-tool.js +11 -8
  54. package/dist/src/tools/search-message-tool.js +1 -1
  55. package/dist/src/tools/search-note-tool.js +1 -1
  56. package/dist/src/tools/search-photo-gallery-tool.js +1 -1
  57. package/dist/src/tools/send-email-tool.js +1 -1
  58. package/dist/src/tools/send-file-to-user-tool.js +2 -2
  59. package/dist/src/tools/send-message-tool.js +1 -1
  60. package/dist/src/tools/session-manager.js +5 -0
  61. package/dist/src/tools/upload-file-tool.js +15 -5
  62. package/dist/src/tools/upload-photo-tool.js +1 -1
  63. package/dist/src/tools/xiaoyi-add-collection-tool.js +1 -1
  64. package/dist/src/tools/xiaoyi-collection-tool.js +1 -1
  65. package/dist/src/tools/xiaoyi-delete-collection-tool.js +1 -1
  66. package/dist/src/tools/xiaoyi-gui-tool.js +1 -1
  67. package/dist/src/trigger-handler.js +4 -7
  68. package/dist/src/utils/config-manager.js +3 -6
  69. package/dist/src/utils/logger.d.ts +8 -0
  70. package/dist/src/utils/logger.js +69 -34
  71. package/dist/src/utils/pushdata-manager.js +1 -5
  72. package/dist/src/utils/pushid-manager.js +1 -2
  73. package/dist/src/utils/runtime-manager.js +1 -4
  74. package/dist/src/websocket.js +37 -25
  75. package/package.json +1 -1
@@ -1,10 +1,21 @@
1
- // CSPL Hook 工具函数
2
- import { MAX_TEXT_LENGTH, regex, SECURITY_NOTICE } from "./constants.js";
1
+ /*
2
+ * 版权所有 (c) 华为技术有限公司 2026-2026
3
+ */
4
+ import { MAX_TEXT_LENGTH, regex, SECURITY_NOTICE, MAX_FILE_COUNT, MAX_COMMAND_LENGTH, CODE_FILE_EXTENSIONS, TOOL_INPUT_DEFAULT, FILE_EXTENSION_REGEX } from './constants.js';
5
+ import crypto from 'crypto';
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { Buffer } from 'buffer';
9
+ import { callApi } from './call_api.js';
10
+ import { uploadFileToObsMain } from './upload_file.js';
11
+ import { logger } from '../utils/logger.js';
12
+ // 文本过滤函数:仅保留中文、英文、数字、标点符号
3
13
  export function filterText(text) {
4
14
  if (!text)
5
15
  return "";
6
- return text.replace(new RegExp(regex.source, "g"), "");
16
+ return text.replace(new RegExp(regex.source, 'g'), '');
7
17
  }
18
+ // 文本验证和截断函数
8
19
  export function validateAndTruncateText(text, maxLength) {
9
20
  if (text.length > maxLength) {
10
21
  const halfMaxLength = Math.floor(maxLength / 2);
@@ -16,7 +27,8 @@ export function validateAndTruncateText(text, maxLength) {
16
27
  }
17
28
  export function extractResultText(event, toolName) {
18
29
  const resultTexts = [];
19
- if (toolName === "web_fetch") {
30
+ // web_fetch工具特殊处理:从details.text提取
31
+ if (toolName === 'web_fetch') {
20
32
  if (event.result?.details?.text) {
21
33
  let text = event.result.details.text;
22
34
  text = text.replace(SECURITY_NOTICE, '');
@@ -24,6 +36,7 @@ export function extractResultText(event, toolName) {
24
36
  }
25
37
  return resultTexts.length > 0 ? resultTexts.join("; ") : "";
26
38
  }
39
+ // 白名单工具:从content[].text提取
27
40
  if (event.result?.content && Array.isArray(event.result.content)) {
28
41
  for (const item of event.result.content) {
29
42
  if (item?.text) {
@@ -33,27 +46,264 @@ export function extractResultText(event, toolName) {
33
46
  }
34
47
  return resultTexts.length > 0 ? resultTexts.join("; ") : "";
35
48
  }
36
- export function processText(resultText) {
49
+ export function processText(resultText, api) {
37
50
  const questionText = filterText(resultText);
38
- const { text: finalText } = validateAndTruncateText(questionText, MAX_TEXT_LENGTH);
51
+ // 检查是否超过4096字符限制,进行截断
52
+ const { text: finalText, truncated } = validateAndTruncateText(questionText, MAX_TEXT_LENGTH);
53
+ if (truncated) {
54
+ logger.warn(`[SENTINEL HOOK] filterText exceeds ${MAX_TEXT_LENGTH}. Original length: ${questionText.length}`);
55
+ }
39
56
  return finalText;
40
57
  }
41
58
  export function parseSecurityResult(response) {
42
59
  if (response === null || response === undefined) {
43
- throw new Error("Response is null or undefined");
60
+ throw new Error('Response is null or undefined');
44
61
  }
45
- if (!response.data || typeof response.data !== "object") {
46
- throw new Error("Response.data is missing or not an object");
62
+ if (response.data === null || response.data === undefined || typeof response.data !== 'object') {
63
+ throw new Error('Response.data is null, undefined or not an object');
47
64
  }
48
- const securityResult = response.data.securityResult;
49
- if (typeof securityResult !== "string") {
50
- throw new Error("Response.data.securityResult is missing or not a string");
65
+ if (!('securityResult' in response.data) || typeof response.data.securityResult !== 'string') {
66
+ throw new Error('Response.data.securityResult is missing or not a string');
51
67
  }
68
+ const securityResult = response.data.securityResult;
52
69
  if (securityResult !== securityResult.trim()) {
53
- throw new Error("Response.data.securityResult contains leading or trailing spaces");
70
+ throw new Error('Response.data.securityResult contains leading or trailing spaces');
54
71
  }
55
- if (securityResult !== "ACCEPT" && securityResult !== "REJECT") {
56
- throw new Error(`Response.data.securityResult must be "ACCEPT" or "REJECT". Actual: "${securityResult}"`);
72
+ if (securityResult !== 'ACCEPT' && securityResult !== 'REJECT') {
73
+ throw new Error(`Response.data.securityResult must be "ACCEPT" or "REJECT". Actual value: "${securityResult}"`);
57
74
  }
58
75
  return { status: securityResult };
59
76
  }
77
+ // 从event对象中提取工具输入参数
78
+ export function extractInputParams(event, toolName) {
79
+ if (toolName === 'exec') {
80
+ return event.params?.command || '';
81
+ }
82
+ else if (toolName === 'message') {
83
+ return event.params?.message || '';
84
+ }
85
+ return '';
86
+ }
87
+ // 从shell命令中提取文件路径
88
+ export function extractFilePathsFromCommand(command) {
89
+ if (!command) {
90
+ return [];
91
+ }
92
+ // 命令字符串超过1K则截断
93
+ let processedCommand = command;
94
+ if (command.length > MAX_COMMAND_LENGTH) {
95
+ processedCommand = command.substring(0, MAX_COMMAND_LENGTH);
96
+ }
97
+ // 使用空格分割命令字符串
98
+ const parts = processedCommand.split(' ');
99
+ const results = [];
100
+ let currentBaseDir = ''; // 当前基础目录
101
+ let expectBaseDir = false; // flag:下一个元素是cd后的基础目录
102
+ // 遍历分割后的命令部分
103
+ for (const part of parts) {
104
+ // 忽略空字符串
105
+ if (!part) {
106
+ continue;
107
+ }
108
+ // 处理cd命令后的基础目录
109
+ if (expectBaseDir) {
110
+ currentBaseDir = part;
111
+ expectBaseDir = false;
112
+ continue;
113
+ }
114
+ // 识别cd命令
115
+ if (part === 'cd') {
116
+ expectBaseDir = true;
117
+ continue;
118
+ }
119
+ // 处理代码文件
120
+ const absolutePath = processCodeFile(part, currentBaseDir);
121
+ if (absolutePath && !results.includes(absolutePath)) {
122
+ results.push(absolutePath);
123
+ if (results.length >= MAX_FILE_COUNT) {
124
+ break;
125
+ }
126
+ }
127
+ }
128
+ return results;
129
+ }
130
+ // 检查是否为代码文件
131
+ function isCodeFile(filePath) {
132
+ const lastDotIndex = filePath.lastIndexOf('.');
133
+ if (lastDotIndex === -1) {
134
+ return { isCodeFile: false, cleanPath: null };
135
+ }
136
+ let orign_extension = filePath.substring(lastDotIndex + 1).toLowerCase();
137
+ orign_extension = orign_extension.replace(FILE_EXTENSION_REGEX, ' ');
138
+ const extension = orign_extension.split(' ')[0];
139
+ if (!CODE_FILE_EXTENSIONS.includes(extension)) {
140
+ return { isCodeFile: false, cleanPath: null };
141
+ }
142
+ const cleanPath = `${filePath.substring(0, lastDotIndex + 1)}${extension}`;
143
+ return { isCodeFile: true, cleanPath: cleanPath };
144
+ }
145
+ // 构建绝对路径
146
+ function buildAbsolutePath(filePath, baseDir) {
147
+ if (path.isAbsolute(filePath)) {
148
+ return filePath;
149
+ }
150
+ if (baseDir) {
151
+ return `${baseDir}/${filePath}`;
152
+ }
153
+ return filePath;
154
+ }
155
+ // 处理代码文件,返回绝对路径
156
+ function processCodeFile(part, currentBaseDir) {
157
+ const { isCodeFile: isCodeFileResult, cleanPath } = isCodeFile(part);
158
+ if (!isCodeFileResult) {
159
+ return null;
160
+ }
161
+ return buildAbsolutePath(cleanPath, currentBaseDir);
162
+ }
163
+ // 计算字符串的SHA256哈希值
164
+ export function calculateContentHash(content) {
165
+ if (!content) {
166
+ return '';
167
+ }
168
+ return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
169
+ }
170
+ // 获取文件大小(KB)
171
+ export function getFileSizeInKB(filePath) {
172
+ try {
173
+ const stats = fs.statSync(filePath);
174
+ return Math.ceil(stats.size / 1024);
175
+ }
176
+ catch (error) {
177
+ return 0;
178
+ }
179
+ }
180
+ // 动态计算content字段长度,确保body总长度<=4096
181
+ export function adjustContentLength(data, api, fields) {
182
+ const adjusted = { ...data };
183
+ let bodyStr = JSON.stringify(adjusted);
184
+ if (bodyStr.length <= MAX_TEXT_LENGTH) {
185
+ return adjusted;
186
+ }
187
+ // 需要截断指定字段
188
+ let lastFieldName = '';
189
+ for (const fieldName of fields) {
190
+ lastFieldName = fieldName;
191
+ bodyStr = JSON.stringify(adjusted);
192
+ const overSize = bodyStr.length - MAX_TEXT_LENGTH;
193
+ const currentFieldValue = adjusted[fieldName];
194
+ if (currentFieldValue && typeof currentFieldValue === 'string' && currentFieldValue.length > overSize) {
195
+ // 从字段头部开始截断
196
+ adjusted[fieldName] = currentFieldValue.substring(0, currentFieldValue.length - overSize);
197
+ logger.warn(`[SENTINEL HOOK] Field "${fieldName}" truncated by ${overSize} characters to fit ${MAX_TEXT_LENGTH} limit`);
198
+ }
199
+ else {
200
+ // 字段太短,清空字段
201
+ adjusted[fieldName] = '';
202
+ logger.warn(`[SENTINEL HOOK] Field "${fieldName}" cleared as it cannot fit within size limit`);
203
+ }
204
+ // 检查是否满足要求
205
+ bodyStr = JSON.stringify(adjusted);
206
+ if (bodyStr.length <= MAX_TEXT_LENGTH) {
207
+ break;
208
+ }
209
+ }
210
+ bodyStr = JSON.stringify(adjusted);
211
+ if (bodyStr.length > MAX_TEXT_LENGTH) {
212
+ throw new Error(`Field ${lastFieldName} exceeds length limit, unable to send data.`);
213
+ }
214
+ return adjusted;
215
+ }
216
+ // 发送TOOL_INPUT请求并处理响应
217
+ async function sendToolInputRequest(postText, api, sessionId) {
218
+ const response = await callApi(postText, api, sessionId);
219
+ const result = parseSecurityResult(response);
220
+ logger.log(`[SENTINEL HOOK] TOOL_INPUT response: status=${result.status}`);
221
+ }
222
+ // 处理exec工具的TOOL_INPUT数据采集
223
+ export async function handleExecToolInput(event, api, sessionId) {
224
+ const command = extractInputParams(event, 'exec');
225
+ if (!command) {
226
+ logger.log('[SENTINEL HOOK] No command found for exec tool');
227
+ return null;
228
+ }
229
+ // 解析命令提取文件路径
230
+ const filePaths = extractFilePathsFromCommand(command);
231
+ if (filePaths.length > 0) {
232
+ // 场景1:执行代码文件
233
+ logger.log(`[SENTINEL HOOK] Found ${filePaths.length} file(s) in command`);
234
+ const nonExistingFiles = [];
235
+ for (const filePath of filePaths) {
236
+ if (!fs.existsSync(filePath)) {
237
+ nonExistingFiles.push(filePath);
238
+ continue;
239
+ }
240
+ const fileContent = fs.readFileSync(filePath, 'utf8');
241
+ const fileHash = calculateContentHash(fileContent);
242
+ const fileSize = getFileSizeInKB(filePath);
243
+ const obsUrl = await uploadFileToObsMain(filePath, api, fileHash, sessionId);
244
+ const toolInputData = { ...TOOL_INPUT_DEFAULT, tool: 'exec', hash: fileHash, url: obsUrl, size: fileSize,
245
+ source: command, content: fileContent };
246
+ const adjustedData = adjustContentLength(toolInputData, api, ['content', 'source']);
247
+ const postText = JSON.stringify(adjustedData);
248
+ logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for file: ${path.basename(filePath)}, body length: ${postText.length}`);
249
+ try {
250
+ await sendToolInputRequest(postText, api, sessionId);
251
+ }
252
+ catch (e) {
253
+ logger.error(`[SENTINEL HOOK] Sending TOOL_INPUT Failed: ${e}`);
254
+ }
255
+ }
256
+ // 输出不存在的文件列表
257
+ if (nonExistingFiles.length > 0) {
258
+ const fileNames = nonExistingFiles.map(f => path.basename(f)).join(', ');
259
+ logger.log(`[SENTINEL HOOK] Non-existing files: ${fileNames}`);
260
+ }
261
+ }
262
+ else {
263
+ // 场景2:直接执行代码(heredoc场景)
264
+ logger.log('[SENTINEL HOOK] No code files found in command, treating as direct code execution');
265
+ const commandHash = calculateContentHash(command);
266
+ const commandSizeKB = Math.ceil(Buffer.byteLength(command, 'utf8') / 1024);
267
+ const toolInputData = { ...TOOL_INPUT_DEFAULT, tool: 'exec', hash: commandHash, size: commandSizeKB, source: command };
268
+ const adjustedData = adjustContentLength(toolInputData, api, ['source']);
269
+ const postText = JSON.stringify(adjustedData);
270
+ logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for direct code execution, body length: ${postText.length}`);
271
+ await sendToolInputRequest(postText, api, sessionId);
272
+ }
273
+ }
274
+ // 处理message工具的TOOL_INPUT数据采集
275
+ export async function handleMessageToolInput(event, api, sessionId) {
276
+ const message = extractInputParams(event, 'message');
277
+ if (!message) {
278
+ logger.log('[SENTINEL HOOK] No message found for message tool');
279
+ return null;
280
+ }
281
+ logger.log(`[SENTINEL HOOK] Processing message tool input, message length: ${message.length}`);
282
+ const messageHash = calculateContentHash(message);
283
+ const messageSizeKB = Math.ceil(Buffer.byteLength(message, 'utf8') / 1024);
284
+ const toolInputData = { ...TOOL_INPUT_DEFAULT, tool: 'message', hash: messageHash, size: messageSizeKB, content: message };
285
+ const adjustedData = adjustContentLength(toolInputData, api, ['content']);
286
+ const postText = JSON.stringify(adjustedData);
287
+ logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for message, body length: ${postText.length}`);
288
+ await sendToolInputRequest(postText, api, sessionId);
289
+ }
290
+ // 处理其他工具(非 exec 和非 message)的 TOOL_INPUT 数据采集
291
+ export async function handleOtherToolInput(event, api, sessionId) {
292
+ const params = event.params;
293
+ if (!params) {
294
+ logger.log('[SENTINEL HOOK] No params found for tool');
295
+ return;
296
+ }
297
+ logger.log(`[SENTINEL HOOK] Processing other tool input, toolName: ${event.toolName}`);
298
+ // 将 params 序列化为 JSON 字符串
299
+ const paramsJson = JSON.stringify(params);
300
+ const paramsHash = calculateContentHash(paramsJson);
301
+ const paramsSizeKB = Math.ceil(Buffer.byteLength(paramsJson, 'utf8') / 1024);
302
+ // 创建 toolInputData,将 params 放到 source 字段
303
+ const toolInputData = { ...TOOL_INPUT_DEFAULT, tool: event.toolName, hash: paramsHash, size: paramsSizeKB, content: paramsJson };
304
+ // 对 source 字段进行长度截断处理
305
+ const adjustedData = adjustContentLength(toolInputData, api, ['content']);
306
+ const postText = JSON.stringify(adjustedData);
307
+ logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for ${event.toolName}, body length: ${postText.length}`);
308
+ await sendToolInputRequest(postText, api, sessionId);
309
+ }
@@ -3,11 +3,48 @@ 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
+ // ─────────────────────────────────────────────────────────────
9
+ // 敏感信息脱敏辅助函数
10
+ // ─────────────────────────────────────────────────────────────
11
+ const MESSAGE_CONTENT_KEYS = new Set(["text", "reasoningText", "content", "message"]);
12
+ function redactMessagePayload(value, currentKey) {
13
+ if (value === null || value === undefined) {
14
+ return value;
15
+ }
16
+ if (typeof value === "string") {
17
+ if (currentKey === undefined || MESSAGE_CONTENT_KEYS.has(currentKey)) {
18
+ return redactSensitiveText(value);
19
+ }
20
+ return value;
21
+ }
22
+ if (Array.isArray(value)) {
23
+ return value.map(item => redactMessagePayload(item, currentKey));
24
+ }
25
+ if (typeof value === "object") {
26
+ const result = {};
27
+ for (const key of Object.keys(value)) {
28
+ result[key] = redactMessagePayload(value[key], key);
29
+ }
30
+ return result;
31
+ }
32
+ return value;
33
+ }
34
+ function buildTextPreview(text) {
35
+ if (typeof text !== "string" || text.length === 0) {
36
+ return "";
37
+ }
38
+ return text.length <= 10 ? text : `${text.slice(0, 5)}***${text.slice(-5)}`;
39
+ }
6
40
  /**
7
41
  * Send an A2A artifact update response.
8
42
  */
9
43
  export async function sendA2AResponse(params) {
10
44
  const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage } = params;
45
+ const log = logger.withContext(sessionId, taskId);
46
+ // 审批桥接:将 OpenClaw 的审批提示翻译成用户友好的确认文案
47
+ const bridgedText = text === undefined ? text : rewriteOutboundApprovalText(sessionId, text);
11
48
  // Build artifact update event
12
49
  const artifact = {
13
50
  taskId,
@@ -21,10 +58,10 @@ export async function sendA2AResponse(params) {
21
58
  },
22
59
  };
23
60
  // Add text part (even if empty string, to maintain parts structure)
24
- if (text !== undefined) {
61
+ if (bridgedText !== undefined) {
25
62
  artifact.artifact.parts.push({
26
63
  kind: "text",
27
- text,
64
+ text: bridgedText,
28
65
  });
29
66
  }
30
67
  // Add file parts if provided
@@ -34,6 +71,8 @@ export async function sendA2AResponse(params) {
34
71
  data: { fileInfo: files },
35
72
  });
36
73
  }
74
+ // 对消息内容字段做敏感信息脱敏,不修改协议层的 id 等字段
75
+ artifact.artifact.parts = redactMessagePayload(artifact.artifact.parts, "parts");
37
76
  // Build JSON-RPC response
38
77
  const jsonRpcResponse = {
39
78
  jsonrpc: "2.0",
@@ -46,7 +85,7 @@ export async function sendA2AResponse(params) {
46
85
  code: errorCode,
47
86
  message: errorMessage ?? "任务执行异常,请重试",
48
87
  };
49
- logger.log(`[A2A_RESPONSE] ⚠️ Including error code: ${errorCode}`);
88
+ log.log(`[A2A_RESPONSE] Including error code: ${errorCode}`);
50
89
  }
51
90
  // Send via WebSocket
52
91
  const wsManager = getXYWebSocketManager(config);
@@ -57,14 +96,11 @@ export async function sendA2AResponse(params) {
57
96
  taskId,
58
97
  msgDetail: JSON.stringify(jsonRpcResponse),
59
98
  };
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}`);
99
+ // Log complete response body
100
+ const redactedText = redactSensitiveText(bridgedText ?? "");
101
+ log.log(`[A2A_RESPONSE] Sending artifact-update, append=${append}, final=${final}, text=${buildTextPreview(redactedText)}, files=${files?.length ?? 0}, sensitive=${containsSensitiveInfo(bridgedText ?? "")}`);
66
102
  await wsManager.sendMessage(sessionId, outboundMessage);
67
- logger.log(`[A2A_RESPONSE] Message sent successfully`);
103
+ log.log(`[A2A_RESPONSE] Message sent successfully`);
68
104
  }
69
105
  /**
70
106
  * Send an A2A artifact-update with reasoningText part.
@@ -73,6 +109,9 @@ export async function sendA2AResponse(params) {
73
109
  */
74
110
  export async function sendReasoningTextUpdate(params) {
75
111
  const { config, sessionId, taskId, messageId, text, append = true } = params;
112
+ const log = logger.withContext(sessionId, taskId);
113
+ // 审批桥接
114
+ const bridgedText = rewriteOutboundApprovalText(sessionId, text);
76
115
  const artifact = {
77
116
  taskId,
78
117
  kind: "artifact-update",
@@ -84,11 +123,13 @@ export async function sendReasoningTextUpdate(params) {
84
123
  parts: [
85
124
  {
86
125
  kind: "reasoningText",
87
- reasoningText: text,
126
+ reasoningText: bridgedText,
88
127
  },
89
128
  ],
90
129
  },
91
130
  };
131
+ // 对消息内容字段做敏感信息脱敏
132
+ artifact.artifact.parts = redactMessagePayload(artifact.artifact.parts, "parts");
92
133
  const jsonRpcResponse = {
93
134
  jsonrpc: "2.0",
94
135
  id: messageId,
@@ -114,21 +155,26 @@ export async function sendStatusUpdate(params) {
114
155
  // fall back to closure-captured values
115
156
  const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
116
157
  const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
158
+ const log = logger.withContext(sessionId, currentTaskId);
159
+ // 审批桥接和脱敏
160
+ const bridgedText = rewriteOutboundApprovalText(sessionId, text);
161
+ const redactedText = redactSensitiveText(bridgedText);
117
162
  // Build status update event following A2A protocol standard
163
+ const statusMessage = redactMessagePayload({
164
+ role: "agent",
165
+ parts: [
166
+ {
167
+ kind: "text",
168
+ text: bridgedText,
169
+ },
170
+ ],
171
+ });
118
172
  const statusUpdate = {
119
173
  taskId: currentTaskId,
120
174
  kind: "status-update",
121
175
  final: false, // Status updates should not end the stream
122
176
  status: {
123
- message: {
124
- role: "agent",
125
- parts: [
126
- {
127
- kind: "text",
128
- text,
129
- },
130
- ],
131
- },
177
+ message: statusMessage,
132
178
  state,
133
179
  },
134
180
  };
@@ -147,10 +193,8 @@ export async function sendStatusUpdate(params) {
147
193
  taskId: currentTaskId,
148
194
  msgDetail: JSON.stringify(jsonRpcResponse),
149
195
  };
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}"`);
196
+ // Log complete response body
197
+ log.log(`[A2A_STATUS] Sending status-update, text="${redactedText}"`);
154
198
  await wsManager.sendMessage(sessionId, outboundMessage);
155
199
  }
156
200
  /**
@@ -162,6 +206,7 @@ export async function sendCommand(params) {
162
206
  // fall back to closure-captured values
163
207
  const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
164
208
  const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
209
+ const log = logger.withContext(sessionId, currentTaskId);
165
210
  // Build artifact update with command as data
166
211
  // Wrap command in commands array as per protocol requirement
167
212
  const artifact = {
@@ -182,6 +227,8 @@ export async function sendCommand(params) {
182
227
  ],
183
228
  },
184
229
  };
230
+ // 对消息内容字段做敏感信息脱敏
231
+ artifact.artifact.parts = redactMessagePayload(artifact.artifact.parts, "parts");
185
232
  // Build JSON-RPC response
186
233
  const jsonRpcResponse = {
187
234
  jsonrpc: "2.0",
@@ -197,16 +244,17 @@ export async function sendCommand(params) {
197
244
  taskId: currentTaskId,
198
245
  msgDetail: JSON.stringify(jsonRpcResponse),
199
246
  };
200
- // 📋 Log complete response body
201
- logger.log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${currentTaskId}`);
247
+ // Log complete response body
248
+ log.log(`[A2A_COMMAND] Sending command`);
202
249
  await wsManager.sendMessage(sessionId, outboundMessage);
203
- logger.log(`[A2A_COMMAND] Command sent successfully`);
250
+ log.log(`[A2A_COMMAND] Command sent successfully`);
204
251
  }
205
252
  /**
206
253
  * Send a clearContext response.
207
254
  */
208
255
  export async function sendClearContextResponse(params) {
209
256
  const { config, sessionId, messageId } = params;
257
+ const log = logger.withContext(sessionId, "");
210
258
  // Build JSON-RPC response for clearContext
211
259
  const jsonRpcResponse = {
212
260
  jsonrpc: "2.0",
@@ -232,13 +280,14 @@ export async function sendClearContextResponse(params) {
232
280
  msgDetail: JSON.stringify(jsonRpcResponse),
233
281
  };
234
282
  await wsManager.sendMessage(sessionId, outboundMessage);
235
- logger.log(`Sent clearContext response: sessionId=${sessionId}`);
283
+ log.log(`[CLEAR_CONTEXT] Sent clearContext response`);
236
284
  }
237
285
  /**
238
286
  * Send a tasks/cancel response.
239
287
  */
240
288
  export async function sendTasksCancelResponse(params) {
241
289
  const { config, sessionId, taskId, messageId } = params;
290
+ const log = logger.withContext(sessionId, taskId);
242
291
  // Build JSON-RPC response for tasks/cancel
243
292
  // Note: Using any to bypass type check as the response format differs from standard A2A types
244
293
  const jsonRpcResponse = {
@@ -265,13 +314,24 @@ export async function sendTasksCancelResponse(params) {
265
314
  msgDetail: JSON.stringify(jsonRpcResponse),
266
315
  };
267
316
  await wsManager.sendMessage(sessionId, outboundMessage);
268
- logger.log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
317
+ log.log(`[TASKS_CANCEL] Sent tasks/cancel response`);
269
318
  }
270
319
  /**
271
320
  * Send a Trigger response with pushData content.
272
321
  */
273
322
  export async function sendTriggerResponse(params) {
274
323
  const { config, sessionId, taskId, messageId, content } = params;
324
+ const log = logger.withContext(sessionId, taskId);
325
+ // 审批桥接和脱敏
326
+ const bridgedContent = rewriteOutboundApprovalText(sessionId, content);
327
+ const redactedContent = redactSensitiveText(bridgedContent);
328
+ // 对消息内容做敏感信息脱敏
329
+ const artifactParts = redactMessagePayload([
330
+ {
331
+ kind: "text",
332
+ text: bridgedContent,
333
+ },
334
+ ], "parts");
275
335
  // Build JSON-RPC response for Trigger
276
336
  const jsonRpcResponse = {
277
337
  jsonrpc: "2.0",
@@ -284,12 +344,7 @@ export async function sendTriggerResponse(params) {
284
344
  final: true,
285
345
  artifact: {
286
346
  artifactId: uuidv4(),
287
- parts: [
288
- {
289
- kind: "text",
290
- text: content,
291
- },
292
- ],
347
+ parts: artifactParts,
293
348
  },
294
349
  },
295
350
  error: {
@@ -306,7 +361,7 @@ export async function sendTriggerResponse(params) {
306
361
  taskId,
307
362
  msgDetail: JSON.stringify(jsonRpcResponse),
308
363
  };
309
- logger.log(`[TRIGGER_RESPONSE] Sending Trigger response: sessionId=${sessionId}, taskId=${taskId}`);
364
+ log.log(`[TRIGGER_RESPONSE] Sending Trigger response, text=${buildTextPreview(redactedContent)}`);
310
365
  await wsManager.sendMessage(sessionId, outboundMessage);
311
- logger.log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
366
+ log.log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
312
367
  }