@ynhcj/xiaoyi-channel 0.0.144-beta → 0.0.146-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.
package/dist/src/bot.js CHANGED
@@ -270,7 +270,7 @@ export async function handleXYMessage(params) {
270
270
  SenderId: parsed.sessionId,
271
271
  Provider: "xiaoyi-channel",
272
272
  Surface: "xiaoyi-channel",
273
- MessageSid: `${parsed.taskId}_${deviceType}`,
273
+ MessageSid: `xiaoyi_${parsed.taskId}_${deviceType}`,
274
274
  Timestamp: Date.now(),
275
275
  WasMentioned: false,
276
276
  CommandAuthorized: true,
@@ -532,7 +532,7 @@ async function dispatchSteerWhenReady(params) {
532
532
  SenderId: sessionId,
533
533
  Provider: "xiaoyi-channel",
534
534
  Surface: "xiaoyi-channel",
535
- MessageSid: `${params.parsed.taskId}_${params.deviceType}`,
535
+ MessageSid: `xiaoyi_${params.parsed.taskId}_${params.deviceType}`,
536
536
  Timestamp: Date.now(),
537
537
  WasMentioned: false,
538
538
  CommandAuthorized: true,
@@ -46,6 +46,11 @@ function getFirstUserText(messages) {
46
46
  }
47
47
  /** Regex to match `[cron:<uuid> <title>]` anywhere in text. */
48
48
  const CRON_TAG_RE = /\[cron:[^\s\]]+\s+([^\]]+)\]/;
49
+ /** Extract the cron job UUID from the first user message, e.g. `[cron:abc123 ...]` → `abc123`. */
50
+ function extractCronUuid(messages) {
51
+ const match = getFirstUserText(messages).match(/\[cron:([^\s\]]+)/i);
52
+ return match ? match[1] : undefined;
53
+ }
49
54
  /** Check if the request is triggered by a cron job by inspecting the first user message. */
50
55
  function isCronTriggered(messages) {
51
56
  return /\[cron:/i.test(getFirstUserText(messages));
@@ -392,7 +397,9 @@ function trimUserMetadata(text) {
392
397
  }
393
398
  /**
394
399
  * Extract A2A taskId and deviceType from Conversation info JSON.
395
- * bot.ts stores them as MessageSid = "taskId_deviceType".
400
+ * bot.ts stores them as MessageSid = "xiaoyi_taskId_deviceType".
401
+ * The "xiaoyi_" prefix ensures extraction only happens for messages
402
+ * routed through xiaoyi-channel, not other channels sharing the provider.
396
403
  */
397
404
  function extractA2AFromConversationInfo(text) {
398
405
  const match = text.match(/Conversation info \(untrusted metadata\):\n```json\n([\s\S]*?)\n```/);
@@ -402,9 +409,9 @@ function extractA2AFromConversationInfo(text) {
402
409
  if (!msgIdMatch)
403
410
  return null;
404
411
  const parts = msgIdMatch[1].split("_");
405
- if (parts.length < 2)
412
+ if (parts.length < 3 || parts[0] !== "xiaoyi")
406
413
  return null;
407
- return { taskId: parts[0], deviceType: parts[1] };
414
+ return { taskId: parts[1], deviceType: parts[2] };
408
415
  }
409
416
  export const xiaoyiProvider = {
410
417
  id: "xiaoyiprovider",
@@ -488,15 +495,20 @@ export const xiaoyiProvider = {
488
495
  }
489
496
  }
490
497
  // ── Build dynamic headers ────────────────────────────
498
+ // Priority:
499
+ // 1. TaskId extracted from Conversation info in user messages (most reliable
500
+ // — based on message content, not mutable global state or cached values)
501
+ // 2. UID-based fallback: sha256(uid).hex[:32]_timestamp
502
+ // 3. Cached extraParams from prepareExtraParams (session keys)
491
503
  if (ctx.extraParams) {
492
504
  const fallbackPrefix = ctx.extraParams[FALLBACK_PREFIX_KEY];
493
- if (typeof fallbackPrefix === "string") {
494
- // Fallback mode: generate fresh timestamp per request
505
+ if (extractedTaskId) {
506
+ // Session mode: taskId extracted from Conversation info
507
+ const traceId = extractedTaskId;
508
+ const sessionId = traceId.split("&")[0];
509
+ const interactionId = traceId.split("&")[1] ?? "";
495
510
  const isCron = isCronTriggered(context.messages);
496
- const fallbackValue = `${fallbackPrefix}_${Date.now()}`;
497
- dynamicHeaders[HEADER_TRACE_ID] = isCron ? `cron_${fallbackValue}` : fallbackValue;
498
- dynamicHeaders[HEADER_SESSION_ID] = fallbackValue;
499
- dynamicHeaders[HEADER_INTERACTION_ID] = fallbackValue;
511
+ dynamicHeaders[HEADER_TRACE_ID] = isCron ? `cron_${traceId}_${Date.now()}` : traceId;
500
512
  if (isCron) {
501
513
  const cronTitle = extractCronTitle(context.messages);
502
514
  if (cronTitle)
@@ -504,14 +516,16 @@ export const xiaoyiProvider = {
504
516
  if (context.messages?.length === 1)
505
517
  dynamicHeaders["x-cron-flag"] = "begin";
506
518
  }
519
+ dynamicHeaders[HEADER_SESSION_ID] = sessionId;
520
+ dynamicHeaders[HEADER_INTERACTION_ID] = interactionId;
507
521
  }
508
- else if (extractedTaskId) {
509
- // Session mode: taskId extracted from Conversation info
510
- const traceId = extractedTaskId;
511
- const sessionId = traceId.split("&")[0];
512
- const interactionId = traceId.split("&")[1] ?? "";
522
+ else if (typeof fallbackPrefix === "string") {
523
+ // Fallback mode: generate fresh timestamp per request
513
524
  const isCron = isCronTriggered(context.messages);
514
- dynamicHeaders[HEADER_TRACE_ID] = isCron ? `cron_${traceId}_${Date.now()}` : traceId;
525
+ const fallbackValue = `${fallbackPrefix}_${Date.now()}`;
526
+ dynamicHeaders[HEADER_TRACE_ID] = isCron ? `cron_${fallbackValue}` : fallbackValue;
527
+ dynamicHeaders[HEADER_SESSION_ID] = fallbackValue;
528
+ dynamicHeaders[HEADER_INTERACTION_ID] = fallbackValue;
515
529
  if (isCron) {
516
530
  const cronTitle = extractCronTitle(context.messages);
517
531
  if (cronTitle)
@@ -519,20 +533,35 @@ export const xiaoyiProvider = {
519
533
  if (context.messages?.length === 1)
520
534
  dynamicHeaders["x-cron-flag"] = "begin";
521
535
  }
522
- dynamicHeaders[HEADER_SESSION_ID] = sessionId;
523
- dynamicHeaders[HEADER_INTERACTION_ID] = interactionId;
524
536
  }
525
537
  else {
526
538
  // Fallback: use extraParams cached values
527
539
  const traceId = ctx.extraParams[HEADER_TRACE_ID];
528
540
  const sessionId = ctx.extraParams[HEADER_SESSION_ID];
529
541
  const interactionId = ctx.extraParams[HEADER_INTERACTION_ID];
530
- if (typeof traceId === "string")
531
- dynamicHeaders[HEADER_TRACE_ID] = traceId;
532
- if (typeof sessionId === "string")
533
- dynamicHeaders[HEADER_SESSION_ID] = sessionId;
534
- if (typeof interactionId === "string")
535
- dynamicHeaders[HEADER_INTERACTION_ID] = interactionId;
542
+ const isCronCached = isCronTriggered(context.messages);
543
+ if (isCronCached) {
544
+ // Cron: generate fresh sessionId from cron UUID so each invocation
545
+ // is independently tracked, regardless of stale activeSessions state.
546
+ const cronUuid = extractCronUuid(context.messages) ?? "cron";
547
+ const cronSessionId = `cron_${cronUuid}_${Date.now()}`;
548
+ dynamicHeaders[HEADER_TRACE_ID] = cronSessionId;
549
+ dynamicHeaders[HEADER_SESSION_ID] = cronUuid;
550
+ dynamicHeaders[HEADER_INTERACTION_ID] = cronSessionId;
551
+ const cronTitle = extractCronTitle(context.messages);
552
+ if (cronTitle)
553
+ dynamicHeaders["x-cron-title"] = encodeURIComponent(cronTitle);
554
+ if (context.messages?.length === 1)
555
+ dynamicHeaders["x-cron-flag"] = "begin";
556
+ }
557
+ else {
558
+ if (typeof traceId === "string")
559
+ dynamicHeaders[HEADER_TRACE_ID] = traceId;
560
+ if (typeof sessionId === "string")
561
+ dynamicHeaders[HEADER_SESSION_ID] = sessionId;
562
+ if (typeof interactionId === "string")
563
+ dynamicHeaders[HEADER_INTERACTION_ID] = interactionId;
564
+ }
536
565
  }
537
566
  }
538
567
  // 记录输入
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.144-beta",
3
+ "version": "0.0.146-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",