@ynhcj/xiaoyi-channel 0.0.141-next → 0.0.142-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.
package/dist/src/bot.js CHANGED
@@ -35,7 +35,8 @@ export async function handleXYMessage(params) {
35
35
  if (!sessionId) {
36
36
  throw new Error("clearContext request missing sessionId in params");
37
37
  }
38
- logger.log(`[BOT] Clear context request`);
38
+ const log = logger.withContext(sessionId, "");
39
+ log.log(`[BOT] Clear context request`);
39
40
  const config = resolveXYConfig(cfg);
40
41
  await sendClearContextResponse({
41
42
  config,
@@ -51,7 +52,8 @@ export async function handleXYMessage(params) {
51
52
  if (!sessionId) {
52
53
  throw new Error("tasks/cancel request missing sessionId in params");
53
54
  }
54
- logger.log(`[BOT] Tasks cancel request, taskId=${taskId}`);
55
+ const log = logger.withContext(sessionId, taskId);
56
+ log.log(`[BOT] Tasks cancel request`);
55
57
  const config = resolveXYConfig(cfg);
56
58
  await sendTasksCancelResponse({
57
59
  config,
@@ -63,19 +65,21 @@ export async function handleXYMessage(params) {
63
65
  }
64
66
  // Parse the A2A message (for regular messages)
65
67
  const parsed = parseA2AMessage(message);
68
+ // Scoped logger for this session — avoids concurrent session log mixing
69
+ const log = logger.withContext(parsed.sessionId, parsed.taskId);
66
70
  // ========== 检测 Trigger 消息 ==========
67
71
  // 如果消息中包含 Trigger 事件数据,直接返回 pushData 内容,不走正常流程
68
72
  const triggerData = extractTriggerData(parsed.parts);
69
73
  if (triggerData) {
70
- logger.log(`[BOT] Detected Trigger message, pushDataId=${triggerData.pushDataId}`);
74
+ log.log(`[BOT] Detected Trigger message, pushDataId=${triggerData.pushDataId}`);
71
75
  try {
72
76
  // 读取 pushData
73
77
  const pushDataItem = await getPushDataById(triggerData.pushDataId);
74
78
  if (!pushDataItem) {
75
- logger.error(`[BOT] pushData not found for ID: ${triggerData.pushDataId}`);
79
+ log.error(`[BOT] pushData not found for ID: ${triggerData.pushDataId}`);
76
80
  return;
77
81
  }
78
- logger.log(`[BOT] Found pushData, sending direct response, pushDataId=${pushDataItem.pushDataId}`);
82
+ log.log(`[BOT] Found pushData, sending direct response, pushDataId=${pushDataItem.pushDataId}`);
79
83
  const config = resolveXYConfig(cfg);
80
84
  // 直接发送响应(final=true,不走 openclaw 流程)
81
85
  await sendA2AResponse({
@@ -87,11 +91,11 @@ export async function handleXYMessage(params) {
87
91
  append: false,
88
92
  final: true,
89
93
  });
90
- logger.log(`[BOT] Trigger response sent successfully`);
94
+ log.log(`[BOT] Trigger response sent successfully`);
91
95
  return; // 提前返回,不继续处理
92
96
  }
93
97
  catch (err) {
94
- logger.error(`[BOT] Failed to handle Trigger message:`, err);
98
+ log.error(`[BOT] Failed to handle Trigger message:`, err);
95
99
  return;
96
100
  }
97
101
  }
@@ -100,7 +104,7 @@ export async function handleXYMessage(params) {
100
104
  const isUpdate = hasActiveTask(parsed.sessionId);
101
105
  const skipReg = params.skipRegistration === true;
102
106
  if (isUpdate) {
103
- logger.log(`[BOT] STEER MODE - Second message detected, new taskId=${parsed.taskId}`);
107
+ log.log(`[BOT] STEER MODE - Second message detected, new taskId=${parsed.taskId}`);
104
108
  }
105
109
  // Steer injections skip taskId registration to avoid overwriting the active taskId
106
110
  if (!skipReg) {
@@ -108,28 +112,28 @@ export async function handleXYMessage(params) {
108
112
  // Extract and update push_id if present
109
113
  const pushId = extractPushId(parsed.parts);
110
114
  if (pushId) {
111
- logger.log(`[BOT] Extracted push_id from user message`);
115
+ log.log(`[BOT] Extracted push_id from user message`);
112
116
  configManager.updatePushId(parsed.sessionId, pushId);
113
117
  // 持久化 pushId 到本地文件(异步,不阻塞主流程)
114
118
  addPushId(pushId).catch((err) => {
115
- logger.error(`[BOT] Failed to persist pushId:`, err);
119
+ log.error(`[BOT] Failed to persist pushId:`, err);
116
120
  });
117
121
  }
118
122
  else {
119
- logger.log(`[BOT] No push_id found in message, using config default`);
123
+ log.log(`[BOT] No push_id found in message, using config default`);
120
124
  }
121
125
  // 保存 runtime 信息到 .xiaoyiruntime 文件(异步,不阻塞主流程)
122
126
  saveRuntimeInfo(webSocketSessionId || parsed.sessionId, // SESSION_ID (WebSocket 层级,如果没有则 fallback)
123
127
  parsed.sessionId, // CONVERSATION_ID (param 里的 sessionId)
124
128
  parsed.taskId // TASK_ID (param.id)
125
129
  ).catch((err) => {
126
- logger.error(`[BOT] Failed to save runtime info:`, err);
130
+ log.error(`[BOT] Failed to save runtime info:`, err);
127
131
  });
128
132
  }
129
133
  // Extract deviceType if present (always parse — used in ctxPayload.MessageSid)
130
134
  const deviceType = extractDeviceType(parsed.parts);
131
135
  if (deviceType) {
132
- logger.log(`[BOT] Extracted deviceType: ${deviceType}`);
136
+ log.log(`[BOT] Extracted deviceType: ${deviceType}`);
133
137
  }
134
138
  // Resolve configuration (needed for status updates)
135
139
  const config = resolveXYConfig(cfg);
@@ -145,7 +149,7 @@ export async function handleXYMessage(params) {
145
149
  id: parsed.sessionId, // ✅ Use sessionId to share context within the same conversation session
146
150
  },
147
151
  });
148
- logger.log(`[BOT] Resolved route, sessionKey=${route.sessionKey}`);
152
+ log.log(`[BOT] Resolved route, sessionKey=${route.sessionKey}`);
149
153
  // Steer injections skip session registration to avoid refCount leaks
150
154
  if (!skipReg) {
151
155
  registerSession(route.sessionKey, {
@@ -157,7 +161,7 @@ export async function handleXYMessage(params) {
157
161
  deviceType,
158
162
  });
159
163
  // 🔑 发送初始状态更新
160
- logger.log(`[BOT] Sending initial status update`);
164
+ log.log(`[BOT] Sending initial status update`);
161
165
  void sendStatusUpdate({
162
166
  config,
163
167
  sessionId: parsed.sessionId,
@@ -166,7 +170,7 @@ export async function handleXYMessage(params) {
166
170
  text: "任务正在处理中,请稍候~",
167
171
  state: "working",
168
172
  }).catch((err) => {
169
- logger.error(`Failed to send initial status update:`, err);
173
+ log.error(`Failed to send initial status update:`, err);
170
174
  });
171
175
  }
172
176
  // Extract text and files from parts
@@ -178,18 +182,18 @@ export async function handleXYMessage(params) {
178
182
  const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
179
183
  if (selfEvolutionEnabled && shouldNudgeForSelfEvolutionKeyword(textForAgent)) {
180
184
  const shouldNudge = toolCallNudgeManager.tryMarkKeywordNudge(route.sessionKey);
181
- logger.log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
185
+ log.log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
182
186
  if (shouldNudge) {
183
187
  const augmented = appendSelfEvolutionKeywordNudge(textForAgent);
184
188
  textForAgent = augmented.text;
185
189
  if (augmented.appended) {
186
- logger.log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
190
+ log.log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
187
191
  }
188
192
  }
189
193
  }
190
194
  }
191
195
  catch (selfEvolutionError) {
192
- logger.error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
196
+ log.error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
193
197
  }
194
198
  }
195
199
  // 🔑 Steer消息: 跳过旧路径直接进入 streaming-signal 队列
@@ -204,10 +208,10 @@ export async function handleXYMessage(params) {
204
208
  const steerDownloadedFiles = await downloadFilesFromParts(steerFileParts);
205
209
  const steerMediaPayload = buildXYMediaPayload(steerDownloadedFiles);
206
210
  if (steerFileParts.length > 0) {
207
- logger.log(`[BOT] Steer message with ${steerFileParts.length} file(s), enqueuing to streaming-signal queue`);
211
+ log.log(`[BOT] Steer message with ${steerFileParts.length} file(s), enqueuing to streaming-signal queue`);
208
212
  }
209
213
  else {
210
- logger.log(`[BOT] Steer message — enqueuing to streaming-signal queue`);
214
+ log.log(`[BOT] Steer message — enqueuing to streaming-signal queue`);
211
215
  }
212
216
  await enqueueSteer({
213
217
  sessionId: parsed.sessionId,
@@ -220,7 +224,7 @@ export async function handleXYMessage(params) {
220
224
  route,
221
225
  deviceType,
222
226
  });
223
- logger.log(`[BOT] Steer queue completed`);
227
+ log.log(`[BOT] Steer queue completed`);
224
228
  return;
225
229
  }
226
230
  // ── First message (non-steer) path below ──────────────────────
@@ -232,7 +236,7 @@ export async function handleXYMessage(params) {
232
236
  if (!skipReg) {
233
237
  const fileParts = extractFileParts(parsed.parts);
234
238
  const downloadedFiles = await downloadFilesFromParts(fileParts);
235
- logger.log(`[BOT] Downloaded ${downloadedFiles.length} file(s)`);
239
+ log.log(`[BOT] Downloaded ${downloadedFiles.length} file(s)`);
236
240
  mediaPayload = buildXYMediaPayload(downloadedFiles);
237
241
  }
238
242
  // Resolve envelope format options (following feishu pattern)
@@ -278,7 +282,7 @@ export async function handleXYMessage(params) {
278
282
  // 🔑 Streaming 信号已在上方创建(在文件下载之前)
279
283
  const steerState = { steered: false };
280
284
  // 🔑 创建dispatcher
281
- logger.log(`[BOT-DISPATCHER] Creating reply dispatcher`);
285
+ log.log(`[BOT-DISPATCHER] Creating reply dispatcher`);
282
286
  const { dispatcher, replyOptions, markDispatchIdle, startStatusInterval } = createXYReplyDispatcher({
283
287
  cfg,
284
288
  runtime,
@@ -301,20 +305,20 @@ export async function handleXYMessage(params) {
301
305
  agentId: route.accountId,
302
306
  deviceType,
303
307
  };
304
- logger.log(`[BOT-DISPATCH] withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
308
+ log.log(`[BOT-DISPATCH] withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
305
309
  await core.channel.reply.withReplyDispatcher({
306
310
  dispatcher,
307
311
  onSettled: () => {
308
- logger.log(`[BOT] onSettled, steered=${steerState.steered}`);
312
+ log.log(`[BOT] onSettled, steered=${steerState.steered}`);
309
313
  // 🔑 When steered, skip heavy cleanup — the first message's dispatcher is still running
310
314
  if (steerState.steered) {
311
- logger.log(`[BOT] Steered dispatch settled, skipping cleanup`);
315
+ log.log(`[BOT] Steered dispatch settled, skipping cleanup`);
312
316
  return;
313
317
  }
314
318
  streamingSignals.delete(parsed.sessionId);
315
319
  decrementTaskIdRef(parsed.sessionId);
316
320
  unregisterSession(route.sessionKey);
317
- logger.log(`[BOT] Cleanup completed`);
321
+ log.log(`[BOT] Cleanup completed`);
318
322
  },
319
323
  run: () => {
320
324
  // 🔐 Use AsyncLocalStorage to provide session context to tools.
@@ -323,7 +327,7 @@ export async function handleXYMessage(params) {
323
327
  // signal init complete to release the global dispatch gate
324
328
  // for the next session.
325
329
  const dispatchPromise = runWithSessionContext(sessionContext, async () => {
326
- logger.log(`[BOT-DISPATCH] dispatchReplyFromConfig starting, body.length=${ctxPayload.Body?.length ?? 0}`);
330
+ log.log(`[BOT-DISPATCH] dispatchReplyFromConfig starting, body.length=${ctxPayload.Body?.length ?? 0}`);
327
331
  try {
328
332
  const result = await core.channel.reply.dispatchReplyFromConfig({
329
333
  ctx: ctxPayload,
@@ -331,11 +335,11 @@ export async function handleXYMessage(params) {
331
335
  dispatcher,
332
336
  replyOptions,
333
337
  });
334
- logger.log(`[BOT-DISPATCH] dispatchReplyFromConfig returned, result=${JSON.stringify(result)}`);
338
+ log.log(`[BOT-DISPATCH] dispatchReplyFromConfig returned, result=${JSON.stringify(result)}`);
335
339
  return result;
336
340
  }
337
341
  catch (dispatchErr) {
338
- logger.error(`[BOT-DISPATCH] dispatchReplyFromConfig threw: ${dispatchErr instanceof Error ? `${dispatchErr.name}: ${dispatchErr.message}` : String(dispatchErr)}`, dispatchErr instanceof Error ? dispatchErr.stack?.slice(0, 500) : undefined);
342
+ log.error(`[BOT-DISPATCH] dispatchReplyFromConfig threw: ${dispatchErr instanceof Error ? `${dispatchErr.name}: ${dispatchErr.message}` : String(dispatchErr)}`, dispatchErr instanceof Error ? dispatchErr.stack?.slice(0, 500) : undefined);
339
343
  throw dispatchErr;
340
344
  }
341
345
  });
@@ -344,19 +348,23 @@ export async function handleXYMessage(params) {
344
348
  return dispatchPromise;
345
349
  },
346
350
  });
347
- logger.log(`[BOT] Dispatcher completed`);
351
+ log.log(`[BOT] Dispatcher completed`);
348
352
  }
349
353
  catch (err) {
350
354
  // ✅ Only log error, don't re-throw to prevent gateway restart
351
- logger.error("Failed to handle XY message:", err);
355
+ // Note: if error occurs before parseA2AMessage, `log` may not be defined yet
356
+ const errSessionId = message.params?.sessionId || "";
357
+ const errTaskId = message.params?.id || message.id || "";
358
+ const errLog = logger.withContext(errSessionId, errTaskId);
359
+ errLog.error("Failed to handle XY message:", err);
352
360
  runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
353
- logger.log(`[BOT] Error occurred, attempting cleanup`);
361
+ errLog.log(`[BOT] Error occurred, attempting cleanup`);
354
362
  // 🔑 错误时也要清理taskId和session
355
363
  try {
356
364
  const params = message.params;
357
365
  const sessionId = params?.sessionId;
358
366
  if (sessionId) {
359
- logger.log(`[BOT] Cleaning up after error`);
367
+ errLog.log(`[BOT] Cleaning up after error`);
360
368
  // 清理 taskId
361
369
  decrementTaskIdRef(sessionId);
362
370
  // 清理 session
@@ -371,11 +379,11 @@ export async function handleXYMessage(params) {
371
379
  },
372
380
  });
373
381
  unregisterSession(route.sessionKey);
374
- logger.log(`[BOT] Cleanup completed after error`);
382
+ errLog.log(`[BOT] Cleanup completed after error`);
375
383
  }
376
384
  }
377
385
  catch (cleanupErr) {
378
- logger.log(`[BOT] Cleanup failed:`, cleanupErr);
386
+ errLog.log(`[BOT] Cleanup failed:`, cleanupErr);
379
387
  // Ignore cleanup errors
380
388
  }
381
389
  // ❌ Don't re-throw: message processing error should not affect gateway stability
@@ -412,20 +420,22 @@ const steerQueues = _g.__xySteerQueues;
412
420
  * 这是模型 API 被调用的精确时刻,此时 isStreaming 一定为 true。
413
421
  */
414
422
  export function notifyModelStreaming(sessionId) {
423
+ const log = logger.withContext(sessionId, "");
415
424
  const signal = streamingSignals.get(sessionId);
416
425
  if (signal) {
417
426
  // 不删除 signal——后续 steer 需要靠它判断模型已在 streaming。
418
427
  // 清理由第一条消息的 onSettled 兜底。
419
428
  signal.notify();
420
- logger.log(`[STEER-QUEUE] Model streaming signal fired`);
429
+ log.log(`[STEER-QUEUE] Model streaming signal fired`);
421
430
  }
422
431
  }
423
432
  function createStreamingSignal(sessionId) {
433
+ const log = logger.withContext(sessionId, "");
424
434
  let resolve;
425
435
  const promise = new Promise(r => { resolve = r; });
426
436
  const signal = { promise, notify: resolve };
427
437
  streamingSignals.set(sessionId, signal);
428
- logger.log(`[STEER-QUEUE] Streaming signal created`);
438
+ log.log(`[STEER-QUEUE] Streaming signal created`);
429
439
  return signal;
430
440
  }
431
441
  /**
@@ -435,13 +445,14 @@ function createStreamingSignal(sessionId) {
435
445
  */
436
446
  function enqueueSteer(params) {
437
447
  const { sessionId } = params;
448
+ const log = logger.withContext(sessionId, params.parsed.taskId);
438
449
  // 取出当前队列尾部(或 undefined),然后链上新的 Promise
439
450
  const prev = steerQueues.get(sessionId);
440
451
  const next = (prev ?? Promise.resolve()).then(() => dispatchSteerWhenReady(params));
441
452
  steerQueues.set(sessionId, next);
442
453
  // 链条结束后清理
443
454
  next.catch((err) => {
444
- logger.error(`[STEER-QUEUE] Steer chain failed: ${String(err)}`);
455
+ log.error(`[STEER-QUEUE] Steer chain failed: ${String(err)}`);
445
456
  }).finally(() => {
446
457
  if (steerQueues.get(sessionId) === next) {
447
458
  steerQueues.delete(sessionId);
@@ -451,37 +462,38 @@ function enqueueSteer(params) {
451
462
  }
452
463
  async function dispatchSteerWhenReady(params) {
453
464
  const { sessionId, sessionKey, steerText } = params;
465
+ const log = logger.withContext(sessionId, params.parsed.taskId);
454
466
  // 1. 等待第一条消息开始 streaming
455
467
  // signal 可能尚未创建(第一条消息还在文件下载等耗时操作中),
456
468
  // 轮询等待直到 signal 出现,最長等待 ~5 秒。
457
469
  let signal = streamingSignals.get(sessionId);
458
470
  if (!signal) {
459
- logger.log(`[STEER-QUEUE] Signal not yet created, polling`);
471
+ log.log(`[STEER-QUEUE] Signal not yet created, polling`);
460
472
  for (let i = 0; i < 50; i++) {
461
473
  await new Promise(r => setTimeout(r, 100));
462
474
  signal = streamingSignals.get(sessionId);
463
475
  if (signal)
464
476
  break;
465
477
  if (!hasActiveTask(sessionId)) {
466
- logger.log(`[STEER-QUEUE] First message completed while waiting, skip steer`);
478
+ log.log(`[STEER-QUEUE] First message completed while waiting, skip steer`);
467
479
  return;
468
480
  }
469
481
  }
470
482
  }
471
483
  if (signal) {
472
- logger.log(`[STEER-QUEUE] Waiting for streaming signal`);
484
+ log.log(`[STEER-QUEUE] Waiting for streaming signal`);
473
485
  await signal.promise;
474
- logger.log(`[STEER-QUEUE] Streaming signal received`);
486
+ log.log(`[STEER-QUEUE] Streaming signal received`);
475
487
  }
476
488
  else {
477
489
  // 轮询超时且 hasActiveTask 仍为 true——说明第一条消息可能卡在异常路径,
478
490
  // 没有创建 signal。此时 dispatch 会与第一条消息的模型调用并发冲突,放弃。
479
- logger.log(`[STEER-QUEUE] Signal never appeared after polling, skip steer to avoid collision`);
491
+ log.log(`[STEER-QUEUE] Signal never appeared after polling, skip steer to avoid collision`);
480
492
  return;
481
493
  }
482
494
  // 2. 第一条消息已结束 → 放弃
483
495
  if (!hasActiveTask(sessionId)) {
484
- logger.log(`[STEER-QUEUE] First message completed, skip steer`);
496
+ log.log(`[STEER-QUEUE] First message completed, skip steer`);
485
497
  return;
486
498
  }
487
499
  // 3. 构建 dispatch 上下文并 dispatch /steer
@@ -543,11 +555,11 @@ async function dispatchSteerWhenReady(params) {
543
555
  agentId: params.route.accountId,
544
556
  deviceType: params.deviceType,
545
557
  };
546
- logger.log(`[STEER-QUEUE] Dispatching steer`);
558
+ log.log(`[STEER-QUEUE] Dispatching steer`);
547
559
  await core.channel.reply.withReplyDispatcher({
548
560
  dispatcher,
549
561
  onSettled: () => {
550
- logger.log(`[STEER-QUEUE] Steer dispatch settled`);
562
+ log.log(`[STEER-QUEUE] Steer dispatch settled`);
551
563
  },
552
564
  run: () => {
553
565
  return runWithSessionContext(sessionContext, async () => {
@@ -557,10 +569,10 @@ async function dispatchSteerWhenReady(params) {
557
569
  dispatcher,
558
570
  replyOptions,
559
571
  });
560
- logger.log(`[STEER-QUEUE] dispatch result: ${JSON.stringify(result)}`);
572
+ log.log(`[STEER-QUEUE] dispatch result: ${JSON.stringify(result)}`);
561
573
  return result;
562
574
  });
563
575
  },
564
576
  });
565
- logger.log(`[STEER-QUEUE] Steer dispatch completed`);
577
+ log.log(`[STEER-QUEUE] Steer dispatch completed`);
566
578
  }
@@ -46,23 +46,19 @@ 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
- // 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`);
53
49
  // 初始taskId和messageId(作为fallback)
54
50
  const initialTaskId = taskId;
55
51
  const initialMessageId = messageId;
56
- /**
57
- * 🔑 核心改造:动态获取当前活跃的taskId和messageId
58
- * 每次需要taskId时,都从TaskManager获取最新值
59
- */
60
52
  const getActiveTaskId = () => {
61
53
  return getCurrentTaskId(sessionId) ?? initialTaskId;
62
54
  };
63
55
  const getActiveMessageId = () => {
64
56
  return getCurrentMessageId(sessionId) ?? initialMessageId;
65
57
  };
58
+ // Create a scoped logger that always uses this session's sessionId
59
+ // and dynamically resolves the latest taskId
60
+ const scopedLog = () => logger.withContext(sessionId, getActiveTaskId());
61
+ scopedLog().log(`[DISPATCHER-CREATE] Creating dispatcher`);
66
62
  const core = getXYRuntime();
67
63
  const config = resolveXYConfig(cfg);
68
64
  // Simplified prefix context for single-account Xiaoyi channel
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.141-next",
3
+ "version": "0.0.142-next",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",