@ynhcj/xiaoyi-channel 0.0.128-next → 0.0.129-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.
@@ -29,6 +29,27 @@ function createSessionQueue() {
29
29
  return next;
30
30
  };
31
31
  }
32
+ /**
33
+ * Per-session serial queue for steer messages only.
34
+ * Steer messages must run concurrently with the main query (which may block for
35
+ * minutes inside the Pi agent loop), but must be serialized among themselves to
36
+ * prevent concurrent dispatchReplyFromConfig calls that can drop mid-stream
37
+ * steer messages under race conditions.
38
+ */
39
+ function createSteerQueue() {
40
+ const queues = new Map();
41
+ return (sessionId, task) => {
42
+ const prev = queues.get(sessionId) ?? Promise.resolve();
43
+ const next = prev.then(task, task);
44
+ queues.set(sessionId, next);
45
+ void next.finally(() => {
46
+ if (queues.get(sessionId) === next) {
47
+ queues.delete(sessionId);
48
+ }
49
+ });
50
+ return next;
51
+ };
52
+ }
32
53
  /**
33
54
  * Monitor XY channel WebSocket connections.
34
55
  * Keeps the connection alive until abortSignal is triggered.
@@ -65,6 +86,9 @@ export async function monitorXYProvider(opts = {}) {
65
86
  const activeMessages = new Set();
66
87
  // Create session queue for ordered message processing
67
88
  const enqueue = createSessionQueue();
89
+ // Steer-only serial queue: keeps steer messages concurrent with the main
90
+ // query but serialized among themselves to avoid race conditions.
91
+ const enqueueSteer = createSteerQueue();
68
92
  // Global gate that serializes dispatch initialization across sessions.
69
93
  // When a new session starts dispatching, it acquires this gate and holds it
70
94
  // until agent setup (agentTools + wrapStreamFn) is complete, then releases it.
@@ -118,11 +142,12 @@ export async function monitorXYProvider(opts = {}) {
118
142
  const steerMode = cfg.messages?.queue?.mode === "steer";
119
143
  const hasActiveRun = hasActiveTask(parsed.sessionId);
120
144
  if (steerMode && hasActiveRun) {
121
- // Steer模式且有活跃任务:不入队列,直接并发执行
122
- logger.log(`[MONITOR-HANDLER] 🔄 STEER MODE: Executing concurrently for messageKey=${messageKey}`);
145
+ // Steer模式且有活跃任务:通过 steer 专用队列串行执行,与主消息并发但
146
+ // 避免多个 steer 同时进入 dispatchReplyFromConfig 导致中间消息丢失
147
+ logger.log(`[MONITOR-HANDLER] 🔄 STEER MODE: Enqueuing steer for messageKey=${messageKey}`);
123
148
  logger.log(`[MONITOR-HANDLER] - sessionId: ${parsed.sessionId}`);
124
- void task().catch((err) => {
125
- logger.error(`XY gateway: concurrent steer task failed for ${messageKey}: ${String(err)}`);
149
+ void enqueueSteer(parsed.sessionId, task).catch((err) => {
150
+ logger.error(`XY gateway: steer queue processing failed for ${messageKey}: ${String(err)}`);
126
151
  activeMessages.delete(messageKey);
127
152
  });
128
153
  }
@@ -8,7 +8,7 @@ import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
8
8
  * 仅用于全局 Map 回退路径的清理,不影响 ALS 路径。
9
9
  * 工具已改为闭包捕获 ctx,此 TTL 仅作为防止 session 泄漏的最后防线。
10
10
  * 正常对话中 registerSession 会刷新 createdAt,所以长对话不受影响。 */
11
- const SESSION_TTL_MS = 60 * 60 * 1000; // 1 hour
11
+ const SESSION_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours
12
12
  // Use globalThis to ensure a single Map instance across all module copies.
13
13
  // The xy_channel plugin may be loaded by openclaw from different module resolution
14
14
  // paths (plugin entry vs tool registration), causing session-manager.ts to be
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.128-next",
3
+ "version": "0.0.129-next",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",