@ynhcj/xiaoyi-channel 0.0.130-next → 0.0.131-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
@@ -331,6 +331,21 @@ export async function handleXYMessage(params) {
331
331
  return dispatchPromise;
332
332
  },
333
333
  });
334
+ // 🔑 Steer 重试:如果首次注入失败(run 还没 streaming),等待后重试
335
+ if (isUpdate) {
336
+ await retrySteerIfNeeded({
337
+ steerState,
338
+ sessionId: parsed.sessionId,
339
+ sessionKey: route.sessionKey,
340
+ steerText: textForAgent,
341
+ ctxPayload,
342
+ cfg,
343
+ runtime,
344
+ parsed,
345
+ route,
346
+ deviceType,
347
+ });
348
+ }
334
349
  logger.log(`[BOT] ✅ Dispatcher completed for session: ${parsed.sessionId}`);
335
350
  logger.log(`xy: dispatch complete (session=${parsed.sessionId})`);
336
351
  }
@@ -386,3 +401,114 @@ function buildXYMediaPayload(mediaList) {
386
401
  MediaTypes: mediaTypes.length > 0 ? mediaTypes : undefined,
387
402
  };
388
403
  }
404
+ const STEER_RETRY_DELAYS = [2000, 3000, 5000];
405
+ /**
406
+ * Steer 注入重试:当首次注入因 run 未 streaming 而失败时,
407
+ * 等待递增延迟后重试,最多尝试 3 次。
408
+ */
409
+ async function retrySteerIfNeeded(params) {
410
+ const { steerState, sessionId, steerText } = params;
411
+ // 首次成功则无需重试
412
+ if (steerState.steerResult === 'success') {
413
+ logger.log(`[STEER-RETRY] ✅ Steer succeeded on first attempt`);
414
+ return;
415
+ }
416
+ // 只有明确失败才重试;无结果可能是异常路径,也尝试重试
417
+ if (steerState.steerResult !== 'fail' && steerState.steerResult !== undefined) {
418
+ return;
419
+ }
420
+ logger.log(`[STEER-RETRY] ⚠️ First steer attempt result=${steerState.steerResult}, starting retry loop`);
421
+ const core = getXYRuntime();
422
+ for (let attempt = 0; attempt < STEER_RETRY_DELAYS.length; attempt++) {
423
+ const delay = STEER_RETRY_DELAYS[attempt];
424
+ logger.log(`[STEER-RETRY] ⏳ Waiting ${delay}ms before retry #${attempt + 1}`);
425
+ await new Promise(resolve => setTimeout(resolve, delay));
426
+ // 第一条消息已结束,无需继续
427
+ if (!hasActiveTask(sessionId)) {
428
+ logger.log(`[STEER-RETRY] ℹ️ First message completed, skip retry`);
429
+ return;
430
+ }
431
+ logger.log(`[STEER-RETRY] 🔄 Retrying steer dispatch #${attempt + 1}`);
432
+ // 构建新的 steer 消息上下文
433
+ const speaker = sessionId;
434
+ const messageBody = `${speaker}: ${steerText}`;
435
+ const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(params.cfg);
436
+ const body = core.channel.reply.formatAgentEnvelope({
437
+ channel: "xiaoyi-channel",
438
+ from: speaker,
439
+ timestamp: new Date(),
440
+ envelope: envelopeOptions,
441
+ body: messageBody,
442
+ });
443
+ const retryCtx = core.channel.reply.finalizeInboundContext({
444
+ Body: body,
445
+ RawBody: steerText,
446
+ CommandBody: steerText,
447
+ From: sessionId,
448
+ To: sessionId,
449
+ SessionKey: params.route.sessionKey,
450
+ AccountId: params.route.accountId,
451
+ ChatType: "direct",
452
+ GroupSubject: undefined,
453
+ SenderName: sessionId,
454
+ SenderId: sessionId,
455
+ Provider: "xiaoyi-channel",
456
+ Surface: "xiaoyi-channel",
457
+ MessageSid: `${params.parsed.taskId}_${params.deviceType}`,
458
+ Timestamp: Date.now(),
459
+ WasMentioned: false,
460
+ CommandAuthorized: true,
461
+ OriginatingChannel: "xiaoyi-channel",
462
+ OriginatingTo: sessionId,
463
+ ReplyToBody: undefined,
464
+ });
465
+ const retryState = { steered: true };
466
+ const { dispatcher: retryDispatcher, replyOptions: retryReplyOptions } = createXYReplyDispatcher({
467
+ cfg: params.cfg,
468
+ runtime: params.runtime,
469
+ sessionId,
470
+ taskId: params.parsed.taskId,
471
+ messageId: params.parsed.messageId,
472
+ accountId: params.route.accountId,
473
+ steerState: retryState,
474
+ });
475
+ const sessionContext = {
476
+ config: resolveXYConfig(params.cfg),
477
+ sessionId,
478
+ taskId: params.parsed.taskId,
479
+ messageId: params.parsed.messageId,
480
+ agentId: params.route.accountId,
481
+ deviceType: params.deviceType,
482
+ };
483
+ try {
484
+ await core.channel.reply.withReplyDispatcher({
485
+ dispatcher: retryDispatcher,
486
+ onSettled: () => {
487
+ logger.log(`[STEER-RETRY] 🏁 Retry dispatch settled, result=${retryState.steerResult}`);
488
+ },
489
+ run: () => {
490
+ return runWithSessionContext(sessionContext, async () => {
491
+ const result = await core.channel.reply.dispatchReplyFromConfig({
492
+ ctx: retryCtx,
493
+ cfg: params.cfg,
494
+ dispatcher: retryDispatcher,
495
+ replyOptions: retryReplyOptions,
496
+ });
497
+ logger.log(`[STEER-RETRY] dispatch result: ${JSON.stringify(result)}`);
498
+ return result;
499
+ });
500
+ },
501
+ });
502
+ }
503
+ catch (err) {
504
+ logger.error(`[STEER-RETRY] ❌ Retry dispatch #${attempt + 1} threw: ${String(err)}`);
505
+ continue;
506
+ }
507
+ if (retryState.steerResult === 'success') {
508
+ logger.log(`[STEER-RETRY] ✅ Steer succeeded on retry #${attempt + 1}`);
509
+ return;
510
+ }
511
+ logger.log(`[STEER-RETRY] ⚠️ Retry #${attempt + 1} result=${retryState.steerResult}, continuing`);
512
+ }
513
+ logger.warn(`[STEER-RETRY] ❌ All retries exhausted for session ${sessionId}`);
514
+ }
@@ -8,6 +8,7 @@ export interface CreateXYReplyDispatcherParams {
8
8
  accountId: string;
9
9
  steerState: {
10
10
  steered: boolean;
11
+ steerResult?: 'success' | 'fail';
11
12
  };
12
13
  }
13
14
  /**
@@ -115,7 +115,14 @@ export function createXYReplyDispatcher(params) {
115
115
  deliver: async (payload, info) => {
116
116
  // 🔑 steered dispatch不发送内容(让主dispatcher处理)
117
117
  if (steerState.steered) {
118
- logger.log(`[DELIVER] Steered dispatch - skipping deliver, info.kind=${info?.kind}`);
118
+ const text = payload.text ?? '';
119
+ if (text.includes('steered current session')) {
120
+ steerState.steerResult = 'success';
121
+ }
122
+ else if (text.includes('not accepting steering') || text.includes('No active run')) {
123
+ steerState.steerResult = 'fail';
124
+ }
125
+ logger.log(`[DELIVER] Steered dispatch - result=${steerState.steerResult}, info.kind=${info?.kind}, text=${text.slice(0, 80)}`);
119
126
  return;
120
127
  }
121
128
  const text = payload.text ?? "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.130-next",
3
+ "version": "0.0.131-next",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",