@ynhcj/xiaoyi-channel 0.0.139-next → 0.0.140-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.
@@ -42,6 +42,7 @@ function buildTextPreview(text) {
42
42
  */
43
43
  export async function sendA2AResponse(params) {
44
44
  const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage } = params;
45
+ const log = logger.withContext(sessionId, taskId);
45
46
  // 审批桥接:将 OpenClaw 的审批提示翻译成用户友好的确认文案
46
47
  const bridgedText = text === undefined ? text : rewriteOutboundApprovalText(sessionId, text);
47
48
  // Build artifact update event
@@ -84,7 +85,7 @@ export async function sendA2AResponse(params) {
84
85
  code: errorCode,
85
86
  message: errorMessage ?? "任务执行异常,请重试",
86
87
  };
87
- logger.log(`[A2A_RESPONSE] Including error code: ${errorCode}`);
88
+ log.log(`[A2A_RESPONSE] Including error code: ${errorCode}`);
88
89
  }
89
90
  // Send via WebSocket
90
91
  const wsManager = getXYWebSocketManager(config);
@@ -97,9 +98,9 @@ export async function sendA2AResponse(params) {
97
98
  };
98
99
  // Log complete response body
99
100
  const redactedText = redactSensitiveText(bridgedText ?? "");
100
- logger.log(`[A2A_RESPONSE] Sending artifact-update, append=${append}, final=${final}, text=${buildTextPreview(redactedText)}, files=${files?.length ?? 0}, sensitive=${containsSensitiveInfo(bridgedText ?? "")}`);
101
+ log.log(`[A2A_RESPONSE] Sending artifact-update, append=${append}, final=${final}, text=${buildTextPreview(redactedText)}, files=${files?.length ?? 0}, sensitive=${containsSensitiveInfo(bridgedText ?? "")}`);
101
102
  await wsManager.sendMessage(sessionId, outboundMessage);
102
- logger.log(`[A2A_RESPONSE] Message sent successfully`);
103
+ log.log(`[A2A_RESPONSE] Message sent successfully`);
103
104
  }
104
105
  /**
105
106
  * Send an A2A artifact-update with reasoningText part.
@@ -108,6 +109,7 @@ export async function sendA2AResponse(params) {
108
109
  */
109
110
  export async function sendReasoningTextUpdate(params) {
110
111
  const { config, sessionId, taskId, messageId, text, append = true } = params;
112
+ const log = logger.withContext(sessionId, taskId);
111
113
  // 审批桥接
112
114
  const bridgedText = rewriteOutboundApprovalText(sessionId, text);
113
115
  const artifact = {
@@ -153,6 +155,7 @@ export async function sendStatusUpdate(params) {
153
155
  // fall back to closure-captured values
154
156
  const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
155
157
  const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
158
+ const log = logger.withContext(sessionId, currentTaskId);
156
159
  // 审批桥接和脱敏
157
160
  const bridgedText = rewriteOutboundApprovalText(sessionId, text);
158
161
  const redactedText = redactSensitiveText(bridgedText);
@@ -191,7 +194,7 @@ export async function sendStatusUpdate(params) {
191
194
  msgDetail: JSON.stringify(jsonRpcResponse),
192
195
  };
193
196
  // Log complete response body
194
- logger.log(`[A2A_STATUS] Sending status-update, taskId=${currentTaskId}, text="${redactedText}"`);
197
+ log.log(`[A2A_STATUS] Sending status-update, text="${redactedText}"`);
195
198
  await wsManager.sendMessage(sessionId, outboundMessage);
196
199
  }
197
200
  /**
@@ -203,6 +206,7 @@ export async function sendCommand(params) {
203
206
  // fall back to closure-captured values
204
207
  const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
205
208
  const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
209
+ const log = logger.withContext(sessionId, currentTaskId);
206
210
  // Build artifact update with command as data
207
211
  // Wrap command in commands array as per protocol requirement
208
212
  const artifact = {
@@ -241,15 +245,16 @@ export async function sendCommand(params) {
241
245
  msgDetail: JSON.stringify(jsonRpcResponse),
242
246
  };
243
247
  // Log complete response body
244
- logger.log(`[A2A_COMMAND] Sending command`);
248
+ log.log(`[A2A_COMMAND] Sending command`);
245
249
  await wsManager.sendMessage(sessionId, outboundMessage);
246
- logger.log(`[A2A_COMMAND] Command sent successfully`);
250
+ log.log(`[A2A_COMMAND] Command sent successfully`);
247
251
  }
248
252
  /**
249
253
  * Send a clearContext response.
250
254
  */
251
255
  export async function sendClearContextResponse(params) {
252
256
  const { config, sessionId, messageId } = params;
257
+ const log = logger.withContext(sessionId, "");
253
258
  // Build JSON-RPC response for clearContext
254
259
  const jsonRpcResponse = {
255
260
  jsonrpc: "2.0",
@@ -275,13 +280,14 @@ export async function sendClearContextResponse(params) {
275
280
  msgDetail: JSON.stringify(jsonRpcResponse),
276
281
  };
277
282
  await wsManager.sendMessage(sessionId, outboundMessage);
278
- logger.log(`[CLEAR_CONTEXT] Sent clearContext response`);
283
+ log.log(`[CLEAR_CONTEXT] Sent clearContext response`);
279
284
  }
280
285
  /**
281
286
  * Send a tasks/cancel response.
282
287
  */
283
288
  export async function sendTasksCancelResponse(params) {
284
289
  const { config, sessionId, taskId, messageId } = params;
290
+ const log = logger.withContext(sessionId, taskId);
285
291
  // Build JSON-RPC response for tasks/cancel
286
292
  // Note: Using any to bypass type check as the response format differs from standard A2A types
287
293
  const jsonRpcResponse = {
@@ -308,13 +314,14 @@ export async function sendTasksCancelResponse(params) {
308
314
  msgDetail: JSON.stringify(jsonRpcResponse),
309
315
  };
310
316
  await wsManager.sendMessage(sessionId, outboundMessage);
311
- logger.log(`[TASKS_CANCEL] Sent tasks/cancel response`);
317
+ log.log(`[TASKS_CANCEL] Sent tasks/cancel response`);
312
318
  }
313
319
  /**
314
320
  * Send a Trigger response with pushData content.
315
321
  */
316
322
  export async function sendTriggerResponse(params) {
317
323
  const { config, sessionId, taskId, messageId, content } = params;
324
+ const log = logger.withContext(sessionId, taskId);
318
325
  // 审批桥接和脱敏
319
326
  const bridgedContent = rewriteOutboundApprovalText(sessionId, content);
320
327
  const redactedContent = redactSensitiveText(bridgedContent);
@@ -354,7 +361,7 @@ export async function sendTriggerResponse(params) {
354
361
  taskId,
355
362
  msgDetail: JSON.stringify(jsonRpcResponse),
356
363
  };
357
- logger.log(`[TRIGGER_RESPONSE] Sending Trigger response, text=${buildTextPreview(redactedContent)}`);
364
+ log.log(`[TRIGGER_RESPONSE] Sending Trigger response, text=${buildTextPreview(redactedContent)}`);
358
365
  await wsManager.sendMessage(sessionId, outboundMessage);
359
- logger.log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
366
+ log.log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
360
367
  }
@@ -46,7 +46,10 @@ export async function cleanupStaleTempFiles(tempDir = "/tmp/xy_channel") {
46
46
  */
47
47
  export function createXYReplyDispatcher(params) {
48
48
  const { cfg, runtime, sessionId, taskId, messageId, accountId, steerState } = params;
49
- logger.log(`[DISPATCHER-CREATE] Creating dispatcher, taskId=${taskId}`);
49
+ // Create a scoped logger that always uses this session's sessionId
50
+ // and dynamically resolves the latest taskId
51
+ const scopedLog = () => logger.withContext(sessionId, getActiveTaskId());
52
+ scopedLog().log(`[DISPATCHER-CREATE] Creating dispatcher`);
50
53
  // 初始taskId和messageId(作为fallback)
51
54
  const initialTaskId = taskId;
52
55
  const initialMessageId = messageId;
@@ -76,12 +79,12 @@ export function createXYReplyDispatcher(params) {
76
79
  * Start the status update interval
77
80
  */
78
81
  const startStatusInterval = () => {
79
- logger.log(`[STATUS-INTERVAL] Starting interval`);
82
+ scopedLog().log(`[STATUS-INTERVAL] Starting interval`);
80
83
  statusUpdateInterval = setInterval(() => {
81
84
  // 🔑 使用动态taskId
82
85
  const currentTaskId = getActiveTaskId();
83
86
  const currentMessageId = getActiveMessageId();
84
- logger.log(`[STATUS-INTERVAL] Triggering status update, taskId=${currentTaskId}`);
87
+ scopedLog().log(`[STATUS-INTERVAL] Triggering status update, taskId=${currentTaskId}`);
85
88
  void sendStatusUpdate({
86
89
  config,
87
90
  sessionId,
@@ -90,13 +93,13 @@ export function createXYReplyDispatcher(params) {
90
93
  text: "任务正在处理中,请稍候~",
91
94
  state: "working",
92
95
  }).catch((err) => {
93
- logger.error(`Failed to send status update:`, err);
96
+ scopedLog().error(`Failed to send status update:`, err);
94
97
  });
95
98
  }, 30000); // 30 seconds
96
99
  };
97
100
  const stopStatusInterval = () => {
98
101
  if (statusUpdateInterval) {
99
- logger.log(`[STATUS-INTERVAL] Stopping interval`);
102
+ scopedLog().log(`[STATUS-INTERVAL] Stopping interval`);
100
103
  clearInterval(statusUpdateInterval);
101
104
  statusUpdateInterval = null;
102
105
  }
@@ -107,26 +110,26 @@ export function createXYReplyDispatcher(params) {
107
110
  humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, accountId),
108
111
  onReplyStart: () => {
109
112
  const currentTaskId = getActiveTaskId();
110
- logger.log(`[REPLY-START] Reply started, taskId=${currentTaskId}, steered=${steerState.steered}`);
113
+ scopedLog().log(`[REPLY-START] Reply started, taskId=${currentTaskId}, steered=${steerState.steered}`);
111
114
  },
112
115
  deliver: async (payload, info) => {
113
116
  // 🔑 steered dispatch不发送内容(让主dispatcher处理)
114
117
  if (steerState.steered) {
115
- logger.log(`[DELIVER] Steered dispatch, skipping, kind=${info?.kind}`);
118
+ scopedLog().log(`[DELIVER] Steered dispatch, skipping, kind=${info?.kind}`);
116
119
  return;
117
120
  }
118
121
  const text = payload.text ?? "";
119
122
  const currentTaskId = getActiveTaskId();
120
123
  const currentMessageId = getActiveMessageId();
121
- logger.log(`[DELIVER] kind=${info?.kind}, text.length=${text.length}`);
124
+ scopedLog().log(`[DELIVER] kind=${info?.kind}, text.length=${text.length}`);
122
125
  try {
123
126
  if (!text.trim()) {
124
- logger.log(`[DELIVER SKIP] Empty text, skipping`);
127
+ scopedLog().log(`[DELIVER SKIP] Empty text, skipping`);
125
128
  return;
126
129
  }
127
130
  accumulatedText += text;
128
131
  hasSentResponse = true;
129
- logger.log(`[DELIVER] Accumulated text, length=${accumulatedText.length}`);
132
+ scopedLog().log(`[DELIVER] Accumulated text, length=${accumulatedText.length}`);
130
133
  // 🔑 使用动态taskId发送reasoningText更新
131
134
  await sendReasoningTextUpdate({
132
135
  config,
@@ -135,10 +138,10 @@ export function createXYReplyDispatcher(params) {
135
138
  messageId: currentMessageId,
136
139
  text,
137
140
  });
138
- logger.log(`[DELIVER] Sent deliver text as reasoningText update`);
141
+ scopedLog().log(`[DELIVER] Sent deliver text as reasoningText update`);
139
142
  }
140
143
  catch (deliverError) {
141
- logger.error(`Failed to deliver message:`, deliverError);
144
+ scopedLog().error(`Failed to deliver message:`, deliverError);
142
145
  }
143
146
  },
144
147
  onError: async (err, info) => {
@@ -146,7 +149,7 @@ export function createXYReplyDispatcher(params) {
146
149
  stopStatusInterval();
147
150
  // 🔑 steered dispatcher不发送错误状态(让主dispatcher处理)
148
151
  if (steerState.steered) {
149
- logger.log(`[ON-ERROR] Steered dispatch, skipping error response`);
152
+ scopedLog().log(`[ON-ERROR] Steered dispatch, skipping error response`);
150
153
  return;
151
154
  }
152
155
  if (!hasSentResponse) {
@@ -163,23 +166,23 @@ export function createXYReplyDispatcher(params) {
163
166
  });
164
167
  }
165
168
  catch (statusError) {
166
- logger.error(`Failed to send error status:`, statusError);
169
+ scopedLog().error(`Failed to send error status:`, statusError);
167
170
  }
168
171
  }
169
172
  },
170
173
  onIdle: async () => {
171
174
  const currentTaskId = getActiveTaskId();
172
175
  const currentMessageId = getActiveMessageId();
173
- logger.log(`[ON-IDLE] Reply idle, steered=${steerState.steered}, hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
176
+ scopedLog().log(`[ON-IDLE] Reply idle, steered=${steerState.steered}, hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
174
177
  // 🔑 steered dispatch不发送final响应(核心已注入到活跃 Pi run)
175
178
  if (steerState.steered) {
176
- logger.log(`[ON-IDLE] Steered dispatch, skipping final response`);
179
+ scopedLog().log(`[ON-IDLE] Steered dispatch, skipping final response`);
177
180
  stopStatusInterval();
178
181
  return; // ← 直接返回,不发送任何东西!
179
182
  }
180
183
  // 正常模式(或未被steer的dispatch)
181
184
  if (hasSentResponse && !finalSent) {
182
- logger.log(`[ON-IDLE] Sending accumulated text, length=${accumulatedText.length}`);
185
+ scopedLog().log(`[ON-IDLE] Sending accumulated text, length=${accumulatedText.length}`);
183
186
  try {
184
187
  // 🔑 使用动态taskId发送完成状态
185
188
  await sendStatusUpdate({
@@ -190,7 +193,7 @@ export function createXYReplyDispatcher(params) {
190
193
  text: "任务处理已完成~",
191
194
  state: "completed",
192
195
  });
193
- logger.log(`[ON-IDLE] Sent completion status update`);
196
+ scopedLog().log(`[ON-IDLE] Sent completion status update`);
194
197
  // 🔑 使用动态taskId发送最终响应
195
198
  await sendA2AResponse({
196
199
  config,
@@ -202,15 +205,15 @@ export function createXYReplyDispatcher(params) {
202
205
  final: true,
203
206
  });
204
207
  finalSent = true;
205
- logger.log(`[ON-IDLE] Sent final response`);
208
+ scopedLog().log(`[ON-IDLE] Sent final response`);
206
209
  }
207
210
  catch (err) {
208
- logger.error(`[ON-IDLE] Failed to send final response:`, err);
211
+ scopedLog().error(`[ON-IDLE] Failed to send final response:`, err);
209
212
  }
210
213
  }
211
214
  else {
212
215
  // 正常失败场景(非steered)
213
- logger.log(`[ON-IDLE] Skipping final message: hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
216
+ scopedLog().log(`[ON-IDLE] Skipping final message: hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
214
217
  try {
215
218
  await sendStatusUpdate({
216
219
  config,
@@ -220,7 +223,7 @@ export function createXYReplyDispatcher(params) {
220
223
  text: "任务处理中断了~",
221
224
  state: "failed",
222
225
  });
223
- logger.log(`[ON-IDLE] Sent failure status update`);
226
+ scopedLog().log(`[ON-IDLE] Sent failure status update`);
224
227
  await sendA2AResponse({
225
228
  config,
226
229
  sessionId,
@@ -233,17 +236,17 @@ export function createXYReplyDispatcher(params) {
233
236
  errorMessage: "任务执行异常,请重试",
234
237
  });
235
238
  finalSent = true;
236
- logger.log(`[ON-IDLE] Sent error response, code=99921111`);
239
+ scopedLog().log(`[ON-IDLE] Sent error response, code=99921111`);
237
240
  }
238
241
  catch (err) {
239
- logger.error(`[ON-IDLE] Failed to send error response:`, err);
242
+ scopedLog().error(`[ON-IDLE] Failed to send error response:`, err);
240
243
  }
241
244
  }
242
245
  stopStatusInterval();
243
246
  },
244
247
  onCleanup: () => {
245
248
  const currentTaskId = getActiveTaskId();
246
- logger.log(`[ON-CLEANUP] Reply cleanup, steered=${steerState.steered}`);
249
+ scopedLog().log(`[ON-CLEANUP] Reply cleanup, steered=${steerState.steered}`);
247
250
  },
248
251
  });
249
252
  return {
@@ -258,13 +261,13 @@ export function createXYReplyDispatcher(params) {
258
261
  }
259
262
  const currentTaskId = getActiveTaskId();
260
263
  const currentMessageId = getActiveMessageId();
261
- logger.log(`[TOOL-START] Tool: ${name}, phase: ${phase}`);
264
+ scopedLog().log(`[TOOL-START] Tool: ${name}, phase: ${phase}`);
262
265
  if (phase === "start") {
263
266
  const toolName = name || "unknown";
264
267
  // call_device_tool 由自身 execute() 内部发送具体子工具名的状态更新
265
268
  // get_xxx_tool_schema 是给 LLM 查 schema 用的,无需向用户展示
266
269
  if (toolName === "call_device_tool" || toolName.endsWith("_tool_schema") || toolName === "huawei_id_tool") {
267
- logger.log(`[TOOL-START] Skipping generic status for ${toolName}`);
270
+ scopedLog().log(`[TOOL-START] Skipping generic status for ${toolName}`);
268
271
  return;
269
272
  }
270
273
  try {
@@ -276,10 +279,10 @@ export function createXYReplyDispatcher(params) {
276
279
  text: `正在使用工具: ${toolName}...`,
277
280
  state: "working",
278
281
  });
279
- logger.log(`[TOOL-START] Sent status update for tool start: ${toolName}`);
282
+ scopedLog().log(`[TOOL-START] Sent status update for tool start: ${toolName}`);
280
283
  }
281
284
  catch (err) {
282
- logger.error(`[TOOL-START] Failed to send tool start status:`, err);
285
+ scopedLog().error(`[TOOL-START] Failed to send tool start status:`, err);
283
286
  }
284
287
  }
285
288
  },
@@ -292,7 +295,7 @@ export function createXYReplyDispatcher(params) {
292
295
  const currentMessageId = getActiveMessageId();
293
296
  const text = payload.text ?? "";
294
297
  const hasMedia = Boolean(payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0);
295
- logger.log(`[TOOL-RESULT] Tool result, text.length: ${text.length}`);
298
+ scopedLog().log(`[TOOL-RESULT] Tool result, text.length: ${text.length}`);
296
299
  try {
297
300
  if (text.length > 0 || hasMedia) {
298
301
  const resultText = text.length > 0 ? text : "工具执行完成";
@@ -304,11 +307,11 @@ export function createXYReplyDispatcher(params) {
304
307
  text: resultText,
305
308
  state: "working",
306
309
  });
307
- logger.log(`[TOOL-RESULT] Sent tool result as status update`);
310
+ scopedLog().log(`[TOOL-RESULT] Sent tool result as status update`);
308
311
  }
309
312
  }
310
313
  catch (err) {
311
- logger.error(`[TOOL-RESULT] Failed to send tool result status:`, err);
314
+ scopedLog().error(`[TOOL-RESULT] Failed to send tool result status:`, err);
312
315
  }
313
316
  },
314
317
  onReasoningStream: async (payload) => {
@@ -317,7 +320,7 @@ export function createXYReplyDispatcher(params) {
317
320
  return;
318
321
  }
319
322
  const text = payload.text ?? "";
320
- logger.log(`[REASONING-STREAM] Reasoning chunk received, text.length: ${text.length}`);
323
+ scopedLog().log(`[REASONING-STREAM] Reasoning chunk received, text.length: ${text.length}`);
321
324
  // Reasoning stream 目前被注释掉
322
325
  // 如果需要可以启用
323
326
  },
@@ -342,7 +345,7 @@ export function createXYReplyDispatcher(params) {
342
345
  }
343
346
  }
344
347
  catch (err) {
345
- logger.error(`[PARTIAL REPLY] Failed to send partial reply:`, err);
348
+ scopedLog().error(`[PARTIAL REPLY] Failed to send partial reply:`, err);
346
349
  }
347
350
  },
348
351
  },
@@ -1,6 +1,14 @@
1
+ export interface ScopedLogger {
2
+ log(message: string, ...args: any[]): void;
3
+ warn(message: string, ...args: any[]): void;
4
+ error(message: string, ...args: any[]): void;
5
+ debug(message: string, ...args: any[]): void;
6
+ }
1
7
  export declare const logger: {
2
8
  log(message: string, ...args: any[]): void;
3
9
  warn(message: string, ...args: any[]): void;
4
10
  error(message: string, ...args: any[]): void;
5
11
  debug(message: string, ...args: any[]): void;
12
+ /** Create a scoped logger with explicit sessionId/taskId (bypasses AsyncLocalStorage/globalThis). */
13
+ withContext(sessionId: string, taskId: string): ScopedLogger;
6
14
  };
@@ -93,14 +93,6 @@ function getSessionInfo() {
93
93
  catch { }
94
94
  return { sessionId: "", taskId: "" };
95
95
  }
96
- // ── Core write function ──
97
- function writeLog(level, message, args) {
98
- checkRotation();
99
- const { sessionId, taskId } = getSessionInfo();
100
- const timestamp = formatTimestampUTC8();
101
- const msg = args.length ? formatMessage(message, args) : message;
102
- dest.write(`| ${level} | ${timestamp} | ${sessionId} | ${taskId} | ${msg}\n`);
103
- }
104
96
  // ── Global error handlers (catch-all) ──
105
97
  process.on("uncaughtException", (err) => {
106
98
  writeLog("fatal", "Uncaught exception", [err]);
@@ -108,6 +100,15 @@ process.on("uncaughtException", (err) => {
108
100
  process.on("unhandledRejection", (reason) => {
109
101
  writeLog("error", "Unhandled rejection", [reason]);
110
102
  });
103
+ // ── Core write function with optional explicit context ──
104
+ function writeLog(level, message, args, explicitSid, explicitTid) {
105
+ checkRotation();
106
+ const sessionId = explicitSid !== undefined ? explicitSid : getSessionInfo().sessionId;
107
+ const taskId = explicitTid !== undefined ? explicitTid : getSessionInfo().taskId;
108
+ const timestamp = formatTimestampUTC8();
109
+ const msg = args.length ? formatMessage(message, args) : message;
110
+ dest.write(`|${level}|${timestamp}|${sessionId}|${taskId}|${msg}\n`);
111
+ }
111
112
  // ── Exported logger ──
112
113
  export const logger = {
113
114
  log(message, ...args) {
@@ -122,6 +123,15 @@ export const logger = {
122
123
  debug(message, ...args) {
123
124
  writeLog("debug", message, args);
124
125
  },
126
+ /** Create a scoped logger with explicit sessionId/taskId (bypasses AsyncLocalStorage/globalThis). */
127
+ withContext(sessionId, taskId) {
128
+ return {
129
+ log(message, ...args) { writeLog("info", message, args, sessionId, taskId); },
130
+ warn(message, ...args) { writeLog("warn", message, args, sessionId, taskId); },
131
+ error(message, ...args) { writeLog("error", message, args, sessionId, taskId); },
132
+ debug(message, ...args) { writeLog("debug", message, args, sessionId, taskId); },
133
+ };
134
+ },
125
135
  };
126
136
  function formatMessage(message, args) {
127
137
  if (!args.length)
@@ -365,8 +365,14 @@ export class XYWebSocketManager extends EventEmitter {
365
365
  handleMessage(data) {
366
366
  try {
367
367
  const messageStr = data.toString();
368
- this.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} characters`);
369
368
  const parsed = JSON.parse(messageStr);
369
+ // Extract sessionId/taskId early for scoped logging
370
+ const sessionId = parsed.params?.sessionId || parsed.sessionId;
371
+ const taskId = parsed.params?.id || parsed.taskId || "";
372
+ const log = sessionId
373
+ ? logger.withContext(sessionId, taskId)
374
+ : { log: (msg, ...args) => logger.log(msg, ...args) };
375
+ log.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} characters`);
370
376
  // 提取并打印消息内容(只显示 text,data 只打印提示)
371
377
  const parts = parsed.params?.message?.parts;
372
378
  if (parts && Array.isArray(parts) && parts.length > 0) {
@@ -391,7 +397,7 @@ export class XYWebSocketManager extends EventEmitter {
391
397
  // 如果长度 > 8,显示前5个 + *** + 后5个
392
398
  maskedText = `${textContents.slice(0, 5)}***${textContents.slice(-5)}`;
393
399
  }
394
- this.log("[WS-RECV] Text:", maskedText);
400
+ log.log(`[WS-RECV] Text: ${maskedText}`);
395
401
  }
396
402
  }
397
403
  }
@@ -399,7 +405,6 @@ export class XYWebSocketManager extends EventEmitter {
399
405
  if (parsed.jsonrpc === "2.0") {
400
406
  const a2aRequest = parsed;
401
407
  // Extract sessionId from params
402
- const sessionId = a2aRequest.params?.sessionId;
403
408
  if (!sessionId) {
404
409
  this.error("[XY] Message missing sessionId");
405
410
  return;
@@ -412,10 +417,10 @@ export class XYWebSocketManager extends EventEmitter {
412
417
  for (const dataPart of dataParts) {
413
418
  const events = dataPart.data?.events;
414
419
  if (!Array.isArray(events)) {
415
- this.log("[XY] dataPart.data.events is not an array, skipping");
420
+ log.log("[XY] dataPart.data.events is not an array, skipping");
416
421
  continue;
417
422
  }
418
- this.log(`[XY] Processing ${events.length} events from data.events`);
423
+ log.log(`[XY] Processing ${events.length} events from data.events`);
419
424
  for (const item of events) {
420
425
  if (item.header?.name === "UploadExeResult" && item.payload?.intentName) {
421
426
  const dataEvent = {
@@ -423,15 +428,15 @@ export class XYWebSocketManager extends EventEmitter {
423
428
  outputs: item.payload.outputs || {},
424
429
  status: "success",
425
430
  };
426
- this.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
431
+ log.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
427
432
  this.emit("data-event", dataEvent);
428
433
  }
429
434
  else if (item.header?.namespace === "ClawAgent" && item.header?.name === "InvokeJarvisGUIAgentResponse") {
430
- this.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
435
+ log.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
431
436
  this.emit("gui-agent-response", item);
432
437
  }
433
438
  else if (item.header?.namespace === "Common" && item.header?.name === "Trigger") {
434
- this.log("[XY] Trigger event detected, emitting trigger-event with context");
439
+ log.log("[XY] Trigger event detected, emitting trigger-event with context");
435
440
  // 传递完整上下文:event、sessionId、taskId
436
441
  this.emit("trigger-event", {
437
442
  event: item,
@@ -440,13 +445,13 @@ export class XYWebSocketManager extends EventEmitter {
440
445
  });
441
446
  }
442
447
  else if (item.header?.namespace === "AgentEvent" && item.header?.name === "ClawSelfEvolutionState") {
443
- this.log("[XY] ClawSelfEvolutionState event detected, emitting self-evolution-event");
448
+ log.log("[XY] ClawSelfEvolutionState event detected, emitting self-evolution-event");
444
449
  this.emit("self-evolution-event", {
445
450
  event: item,
446
451
  });
447
452
  }
448
453
  else if (item.header?.namespace === "AgentEvent" && item.header?.name === "ClawSelfEvolutionStateGet") {
449
- this.log("[XY] ClawSelfEvolutionStateGet event detected, emitting self-evolution-state-get-event");
454
+ log.log("[XY] ClawSelfEvolutionStateGet event detected, emitting self-evolution-state-get-event");
450
455
  this.emit("self-evolution-state-get-event", {
451
456
  event: item,
452
457
  sessionId: sessionId,
@@ -455,13 +460,13 @@ export class XYWebSocketManager extends EventEmitter {
455
460
  });
456
461
  }
457
462
  else if (item.header?.namespace === "LoginTokenEvent" && item.header?.name === "ClawAutoLogin") {
458
- this.log("[XY] LoginTokenEvent.ClawAutoLogin detected, emitting login-token-event");
463
+ log.log("[XY] LoginTokenEvent.ClawAutoLogin detected, emitting login-token-event");
459
464
  this.emit("login-token-event", {
460
465
  event: item,
461
466
  });
462
467
  }
463
468
  else if (item.header?.namespace === "System" && item.header?.name === "ExecuteAgentAsSkillResponse") {
464
- this.log("[XY] ExecuteAgentAsSkillResponse detected, emitting agent-as-skill-response");
469
+ log.log("[XY] ExecuteAgentAsSkillResponse detected, emitting agent-as-skill-response");
465
470
  this.emit("agent-as-skill-response", item);
466
471
  }
467
472
  }
@@ -474,16 +479,16 @@ export class XYWebSocketManager extends EventEmitter {
474
479
  }
475
480
  // Wrapped format (InboundWebSocketMessage)
476
481
  const inboundMsg = parsed;
477
- this.log(`[XY] Message type: Wrapped, msgType: ${inboundMsg.msgType}`);
482
+ log.log(`[XY] Message type: Wrapped, msgType: ${inboundMsg.msgType}`);
478
483
  // Handle heartbeat responses
479
484
  if (inboundMsg.msgType === "heartbeat") {
480
- this.log("[XY] Received heartbeat response");
485
+ log.log("[XY] Received heartbeat response");
481
486
  this.onHealthEvent?.();
482
487
  return;
483
488
  }
484
489
  // Handle data messages
485
490
  if (inboundMsg.msgType === "data") {
486
- this.log("[XY] Processing data message");
491
+ log.log("[XY] Processing data message");
487
492
  try {
488
493
  const a2aRequest = JSON.parse(inboundMsg.msgDetail);
489
494
  const dataParts = a2aRequest.params?.message?.parts?.filter((p) => p.kind === "data");
@@ -491,10 +496,10 @@ export class XYWebSocketManager extends EventEmitter {
491
496
  for (const dataPart of dataParts) {
492
497
  const events = dataPart.data?.events;
493
498
  if (!Array.isArray(events)) {
494
- this.log("[XY] dataPart.data.events is not an array, skipping");
499
+ log.log("[XY] dataPart.data.events is not an array, skipping");
495
500
  continue;
496
501
  }
497
- this.log(`[XY] Processing ${events.length} events from data.events`);
502
+ log.log(`[XY] Processing ${events.length} events from data.events`);
498
503
  for (const item of events) {
499
504
  if (item.header?.name === "UploadExeResult" && item.payload?.intentName) {
500
505
  const dataEvent = {
@@ -502,15 +507,15 @@ export class XYWebSocketManager extends EventEmitter {
502
507
  outputs: item.payload.outputs || {},
503
508
  status: "success",
504
509
  };
505
- this.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
510
+ log.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
506
511
  this.emit("data-event", dataEvent);
507
512
  }
508
513
  else if (item.header?.namespace === "ClawAgent" && item.header?.name === "InvokeJarvisGUIAgentResponse") {
509
- this.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
514
+ log.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
510
515
  this.emit("gui-agent-response", item);
511
516
  }
512
517
  else if (item.header?.namespace === "Common" && item.header?.name === "Trigger") {
513
- this.log("[XY] Trigger event detected (wrapped format), emitting trigger-event with context");
518
+ log.log("[XY] Trigger event detected (wrapped format), emitting trigger-event with context");
514
519
  // 传递完整上下文:event、sessionId、taskId
515
520
  this.emit("trigger-event", {
516
521
  event: item,
@@ -519,13 +524,13 @@ export class XYWebSocketManager extends EventEmitter {
519
524
  });
520
525
  }
521
526
  else if (item.header?.namespace === "LoginTokenEvent" && item.header?.name === "ClawAutoLogin") {
522
- this.log("[XY] LoginTokenEvent.ClawAutoLogin detected (wrapped format), emitting login-token-event");
527
+ log.log("[XY] LoginTokenEvent.ClawAutoLogin detected (wrapped format), emitting login-token-event");
523
528
  this.emit("login-token-event", {
524
529
  event: item,
525
530
  });
526
531
  }
527
532
  else if (item.header?.namespace === "System" && item.header?.name === "ExecuteAgentAsSkillResponse") {
528
- this.log("[XY] ExecuteAgentAsSkillResponse detected (wrapped format), emitting agent-as-skill-response");
533
+ log.log("[XY] ExecuteAgentAsSkillResponse detected (wrapped format), emitting agent-as-skill-response");
529
534
  this.emit("agent-as-skill-response", item);
530
535
  }
531
536
  }
@@ -539,11 +544,9 @@ export class XYWebSocketManager extends EventEmitter {
539
544
  }
540
545
  // Parse msgDetail as A2AJsonRpcRequest
541
546
  const a2aRequest = JSON.parse(inboundMsg.msgDetail);
542
- this.log(`[XY] Parsed A2A request, method: ${a2aRequest.method}`);
543
- const sessionId = inboundMsg.sessionId;
544
- this.log(`[XY] Session ID: ${sessionId}`);
547
+ log.log(`[XY] Parsed A2A request, method: ${a2aRequest.method}`);
545
548
  // Emit message event
546
- this.log("[XY] *** EMITTING message event (Wrapped path) ***");
549
+ log.log("[XY] Emitting message event (Wrapped path)");
547
550
  this.emit("message", a2aRequest, sessionId);
548
551
  }
549
552
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.139-next",
3
+ "version": "0.0.140-next",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",