@ynhcj/xiaoyi-channel 0.0.174-next → 0.0.175-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
@@ -343,6 +343,17 @@ export async function handleXYMessage(params) {
343
343
  const steerState = { steered: false };
344
344
  // 🔑 创建dispatcher
345
345
  log.log(`[BOT-DISPATCHER] Creating reply dispatcher`);
346
+ // Cleanup: 必须在 onIdle 内部执行(参见 reply-dispatcher.ts 中 onIdleComplete 的注释)
347
+ let cleaned = false;
348
+ const cleanup = () => {
349
+ if (cleaned)
350
+ return;
351
+ cleaned = true;
352
+ log.log(`[BOT] Cleanup started`);
353
+ streamingSignals.delete(parsed.sessionId);
354
+ decrementTaskIdRef(parsed.sessionId);
355
+ log.log(`[BOT] Cleanup completed`);
356
+ };
346
357
  const { dispatcher, replyOptions, markDispatchIdle, startStatusInterval } = createXYReplyDispatcher({
347
358
  cfg,
348
359
  runtime,
@@ -351,6 +362,7 @@ export async function handleXYMessage(params) {
351
362
  messageId: parsed.messageId,
352
363
  accountId: route.accountId,
353
364
  steerState,
365
+ onIdleComplete: cleanup,
354
366
  });
355
367
  // Steer injections don't need status intervals
356
368
  if (!skipReg) {
@@ -375,14 +387,14 @@ export async function handleXYMessage(params) {
375
387
  dispatcher,
376
388
  onSettled: () => {
377
389
  log.log(`[BOT] onSettled, steered=${steerState.steered}`);
378
- // 🔑 When steered, skip heavy cleanup — the first message's dispatcher is still running
390
+ // 🔑 When steered, skip cleanup — the first message's dispatcher is still running
379
391
  if (steerState.steered) {
380
392
  log.log(`[BOT] Steered dispatch settled, skipping cleanup`);
381
393
  return;
382
394
  }
383
- streamingSignals.delete(parsed.sessionId);
384
- decrementTaskIdRef(parsed.sessionId);
385
- log.log(`[BOT] Cleanup completed`);
395
+ // Fallback cleanup: 正常流程下 onIdleComplete 已经执行过,cleaned=true 会跳过。
396
+ // 仅在 onIdle 未正常完成时(如异常),这里做兜底清理。
397
+ cleanup();
386
398
  },
387
399
  run: () => {
388
400
  // 🔐 Use AsyncLocalStorage to provide session context to tools.
@@ -9,6 +9,9 @@ export interface CreateXYReplyDispatcherParams {
9
9
  steerState: {
10
10
  steered: boolean;
11
11
  };
12
+ /** Called at end of onIdle, after final frame is sent. openclaw's waitForIdle() does
13
+ * not await the async onIdle, so cleanup must happen inside onIdle itself. */
14
+ onIdleComplete?: () => void | Promise<void>;
12
15
  }
13
16
  /**
14
17
  * 清理 /tmp/xy_channel 目录中超过 24 小时的旧文件
@@ -96,7 +96,7 @@ export async function cleanupStaleTempFiles(tempDir = "/tmp/xy_channel") {
96
96
  * Runtime is expected to be validated before calling this function.
97
97
  */
98
98
  export function createXYReplyDispatcher(params) {
99
- const { cfg, runtime, sessionId, taskId, messageId, accountId, steerState } = params;
99
+ const { cfg, runtime, sessionId, taskId, messageId, accountId, steerState, onIdleComplete } = params;
100
100
  // 初始taskId和messageId(作为fallback)
101
101
  const initialTaskId = taskId;
102
102
  const initialMessageId = messageId;
@@ -325,6 +325,10 @@ export function createXYReplyDispatcher(params) {
325
325
  }
326
326
  }
327
327
  stopStatusInterval();
328
+ // 🔑 清理必须在 onIdle 内部完成,因为 openclaw 的 waitForIdle() 不会
329
+ // await onIdle 返回的 Promise(源码中为 void options.onIdle?.()),
330
+ // 导致 onSettled 在 onIdle 的 async 工作完成之前就执行。
331
+ await onIdleComplete?.();
328
332
  },
329
333
  onCleanup: () => {
330
334
  scopedLog().log(`[ON-CLEANUP] Reply cleanup, steered=${steerState.steered}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.174-next",
3
+ "version": "0.0.175-next",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",