@ynhcj/xiaoyi-channel 0.0.124-beta → 0.0.125-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.
@@ -97,6 +97,7 @@ async function downloadRemoteFile(url) {
97
97
  */
98
98
  export function createSendFileToUserTool(ctx) {
99
99
  const { config, sessionId, taskId, messageId } = ctx;
100
+ console.log(`[SEND-FILE-TO-USER] 🏭 CREATE: sessionId=${sessionId} taskId=${taskId}`);
100
101
  return {
101
102
  name: "send_file_to_user",
102
103
  label: "Send File to User",
@@ -235,6 +236,7 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
235
236
  error: { code: 0 },
236
237
  }),
237
238
  };
239
+ console.log(`[SEND-FILE-TO-USER] 🚀 EXEC sending: sessionId=${sessionId} taskId=${taskId} fileName=${fileName}`);
238
240
  // Send WebSocket message
239
241
  await wsManager.sendMessage(sessionId, agentResponse);
240
242
  console.log(`send ${fileName} file to user success`);
@@ -19,6 +19,13 @@ if (!_g.__xyActiveSessions) {
19
19
  _g.__xyActiveSessions = new Map();
20
20
  }
21
21
  const activeSessions = _g.__xyActiveSessions;
22
+ // Track the most recently registered sessionKey for reliable fallback
23
+ // when AsyncLocalStorage context is lost across openclaw's embedded runner boundary.
24
+ if (!_g.__xyLastRegisteredSessionKey) {
25
+ _g.__xyLastRegisteredSessionKey = "";
26
+ }
27
+ const getLastRegisteredKey = () => _g.__xyLastRegisteredSessionKey;
28
+ const setLastRegisteredKey = (key) => { _g.__xyLastRegisteredSessionKey = key; };
22
29
  // AsyncLocalStorage for thread-safe session context isolation
23
30
  const asyncLocalStorage = new AsyncLocalStorage();
24
31
  /**
@@ -26,6 +33,8 @@ const asyncLocalStorage = new AsyncLocalStorage();
26
33
  * Should be called when starting to process a message.
27
34
  */
28
35
  export function registerSession(sessionKey, context) {
36
+ // Track last registered session for reliable ALS-miss fallback
37
+ setLastRegisteredKey(sessionKey);
29
38
  const existing = activeSessions.get(sessionKey);
30
39
  if (existing) {
31
40
  // 更新上下文,增加引用计数,刷新存活时间
@@ -94,6 +103,7 @@ export function getLatestSessionContext() {
94
103
  * This ensures thread-safe context isolation for concurrent requests.
95
104
  */
96
105
  export function runWithSessionContext(context, callback) {
106
+ console.log(`[SESSION-MGR] 🔵 ALS SET: sessionId=${context.sessionId} taskId=${context.taskId}`);
97
107
  return asyncLocalStorage.run(context, callback);
98
108
  }
99
109
  /**
@@ -112,6 +122,9 @@ export function getCurrentSessionContext(sessionKey) {
112
122
  if (alsContext) {
113
123
  return enrichWithLatestTaskInfo(alsContext);
114
124
  }
125
+ // ALS not available — logging to understand when/why
126
+ const stack = new Error().stack?.split("\n").slice(2, 5).map(s => s.trim()).join(" | ");
127
+ console.log(`[SESSION-MGR] ⚠️ ALS miss, falling back to Map (size=${activeSessions.size}), callers: ${stack}`);
115
128
  // 2. Fallback: look up from global activeSessions Map
116
129
  if (activeSessions.size === 0) {
117
130
  return null;
@@ -142,29 +155,32 @@ export function getCurrentSessionContext(sessionKey) {
142
155
  }
143
156
  return null;
144
157
  }
145
- // 2c. Multiple sessions — find the most recently active one by task-manager activity
146
- // Prefer sessions whose taskId matches the current active task (from task-manager),
147
- // with recency as tiebreaker.
148
- let bestMatch = null;
158
+ // 2c. Multiple sessions — prefer the last registered session.
159
+ // This is the most reliable heuristic when ALS is lost across openclaw's
160
+ // embedded runner boundary: registerSession() is called just before
161
+ // runWithSessionContext(), and agentTools() is called during tool
162
+ // compilation shortly after. The last registered session is always the
163
+ // one currently being set up.
164
+ const lastKey = getLastRegisteredKey();
165
+ if (lastKey) {
166
+ const lastEntry = activeSessions.get(lastKey);
167
+ if (lastEntry) {
168
+ console.log(`[SESSION-MGR] 🎯 using lastRegistered session: ${lastKey}`);
169
+ const { refCount, createdAt, ...context } = lastEntry;
170
+ return enrichWithLatestTaskInfo(context);
171
+ }
172
+ }
173
+ // 2d. Fallback: find any non-stale session
149
174
  const now = Date.now();
150
175
  for (const [key, entry] of activeSessions) {
151
- // Skip stale sessions
152
176
  if (now - entry.createdAt > SESSION_TTL_MS) {
153
- logger.log(`[SESSION-MGR] stale session detected, cleaning up: ${key}`);
154
177
  configManager.clearSession(entry.sessionId);
155
178
  toolCallNudgeManager.clearSession(key);
156
179
  activeSessions.delete(key);
157
180
  continue;
158
181
  }
159
- const latestTaskId = getCurrentTaskId(entry.sessionId);
160
- const recency = latestTaskId ? 2 : 1; // sessions with active task get higher priority
161
- if (!bestMatch || recency > bestMatch.recency) {
162
- const { refCount, createdAt, ...context } = entry;
163
- bestMatch = { context, recency };
164
- }
165
- }
166
- if (bestMatch) {
167
- return enrichWithLatestTaskInfo(bestMatch.context);
182
+ const { refCount, createdAt, ...context } = entry;
183
+ return enrichWithLatestTaskInfo(context);
168
184
  }
169
185
  return null;
170
186
  }
@@ -113,6 +113,7 @@ export class XYWebSocketManager extends EventEmitter {
113
113
  throw new Error("WebSocket not ready");
114
114
  }
115
115
  const messageStr = JSON.stringify(message);
116
+ this.log(`[WS-SEND] sessionId=${sessionId} taskId=${message.taskId} msgType=${message.msgType} len=${messageStr.length}`);
116
117
  this.ws.send(messageStr);
117
118
  }
118
119
  /**
@@ -298,9 +299,9 @@ export class XYWebSocketManager extends EventEmitter {
298
299
  msgDetail: JSON.stringify({ agentId: this.config.agentId }),
299
300
  };
300
301
  const initMessageStr = JSON.stringify(initMessage);
301
- console.log("[WS-SEND] Sending init message frame:", JSON.stringify(initMessage, null, 2));
302
+ this.log("[WS-SEND] Sending init message frame:", JSON.stringify(initMessage, null, 2));
302
303
  this.ws.send(initMessageStr);
303
- console.log(`[WS-SEND] Init message sent successfully, size: ${initMessageStr.length} bytes`);
304
+ this.log(`[WS-SEND] Init message sent successfully, size: ${initMessageStr.length} bytes`);
304
305
  // Mark as ready after init
305
306
  this.state.ready = true;
306
307
  this.emit("ready");
@@ -360,7 +361,7 @@ export class XYWebSocketManager extends EventEmitter {
360
361
  handleMessage(data) {
361
362
  try {
362
363
  const messageStr = data.toString();
363
- console.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} characters`);
364
+ this.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} characters`);
364
365
  const parsed = JSON.parse(messageStr);
365
366
  // 提取并打印消息内容(只显示 text,data 只打印提示)
366
367
  const parts = parsed.params?.message?.parts;
@@ -386,7 +387,7 @@ export class XYWebSocketManager extends EventEmitter {
386
387
  // 如果长度 > 8,显示前5个 + *** + 后5个
387
388
  maskedText = `${textContents.slice(0, 5)}***${textContents.slice(-5)}`;
388
389
  }
389
- console.log("[WS-RECV] Text:", maskedText);
390
+ this.log("[WS-RECV] Text:", maskedText);
390
391
  }
391
392
  }
392
393
  }
@@ -396,7 +397,7 @@ export class XYWebSocketManager extends EventEmitter {
396
397
  // Extract sessionId from params
397
398
  const sessionId = a2aRequest.params?.sessionId;
398
399
  if (!sessionId) {
399
- console.error("[XY] Message missing sessionId");
400
+ this.error("[XY] Message missing sessionId");
400
401
  return;
401
402
  }
402
403
  // Check if message contains only data parts (tool results)
@@ -407,10 +408,10 @@ export class XYWebSocketManager extends EventEmitter {
407
408
  for (const dataPart of dataParts) {
408
409
  const events = dataPart.data?.events;
409
410
  if (!Array.isArray(events)) {
410
- console.warn("[XY] dataPart.data.events is not an array, skipping");
411
+ this.log("[XY] dataPart.data.events is not an array, skipping");
411
412
  continue;
412
413
  }
413
- console.log(`[XY] Processing ${events.length} events from data.events`);
414
+ this.log(`[XY] Processing ${events.length} events from data.events`);
414
415
  for (const item of events) {
415
416
  if (item.header?.name === "UploadExeResult" && item.payload?.intentName) {
416
417
  const dataEvent = {
@@ -418,15 +419,15 @@ export class XYWebSocketManager extends EventEmitter {
418
419
  outputs: item.payload.outputs || {},
419
420
  status: "success",
420
421
  };
421
- console.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
422
+ this.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
422
423
  this.emit("data-event", dataEvent);
423
424
  }
424
425
  else if (item.header?.namespace === "ClawAgent" && item.header?.name === "InvokeJarvisGUIAgentResponse") {
425
- console.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
426
+ this.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
426
427
  this.emit("gui-agent-response", item);
427
428
  }
428
429
  else if (item.header?.namespace === "Common" && item.header?.name === "Trigger") {
429
- console.log("[XY] Trigger event detected, emitting trigger-event with context");
430
+ this.log("[XY] Trigger event detected, emitting trigger-event with context");
430
431
  // 传递完整上下文:event、sessionId、taskId
431
432
  this.emit("trigger-event", {
432
433
  event: item,
@@ -435,13 +436,13 @@ export class XYWebSocketManager extends EventEmitter {
435
436
  });
436
437
  }
437
438
  else if (item.header?.namespace === "AgentEvent" && item.header?.name === "ClawSelfEvolutionState") {
438
- console.log("[XY] ClawSelfEvolutionState event detected, emitting self-evolution-event");
439
+ this.log("[XY] ClawSelfEvolutionState event detected, emitting self-evolution-event");
439
440
  this.emit("self-evolution-event", {
440
441
  event: item,
441
442
  });
442
443
  }
443
444
  else if (item.header?.namespace === "AgentEvent" && item.header?.name === "ClawSelfEvolutionStateGet") {
444
- console.log("[XY] ClawSelfEvolutionStateGet event detected, emitting self-evolution-state-get-event");
445
+ this.log("[XY] ClawSelfEvolutionStateGet event detected, emitting self-evolution-state-get-event");
445
446
  this.emit("self-evolution-state-get-event", {
446
447
  event: item,
447
448
  sessionId: sessionId,
@@ -450,7 +451,7 @@ export class XYWebSocketManager extends EventEmitter {
450
451
  });
451
452
  }
452
453
  else if (item.header?.namespace === "LoginTokenEvent" && item.header?.name === "ClawAutoLogin") {
453
- console.log("[XY] LoginTokenEvent.ClawAutoLogin detected, emitting login-token-event");
454
+ this.log("[XY] LoginTokenEvent.ClawAutoLogin detected, emitting login-token-event");
454
455
  this.emit("login-token-event", {
455
456
  event: item,
456
457
  });
@@ -465,16 +466,16 @@ export class XYWebSocketManager extends EventEmitter {
465
466
  }
466
467
  // Wrapped format (InboundWebSocketMessage)
467
468
  const inboundMsg = parsed;
468
- console.log(`[XY] Message type: Wrapped, msgType: ${inboundMsg.msgType}`);
469
+ this.log(`[XY] Message type: Wrapped, msgType: ${inboundMsg.msgType}`);
469
470
  // Handle heartbeat responses
470
471
  if (inboundMsg.msgType === "heartbeat") {
471
- console.log("[XY] Received heartbeat response");
472
+ this.log("[XY] Received heartbeat response");
472
473
  this.onHealthEvent?.();
473
474
  return;
474
475
  }
475
476
  // Handle data messages
476
477
  if (inboundMsg.msgType === "data") {
477
- console.log("[XY] Processing data message");
478
+ this.log("[XY] Processing data message");
478
479
  try {
479
480
  const a2aRequest = JSON.parse(inboundMsg.msgDetail);
480
481
  const dataParts = a2aRequest.params?.message?.parts?.filter((p) => p.kind === "data");
@@ -482,10 +483,10 @@ export class XYWebSocketManager extends EventEmitter {
482
483
  for (const dataPart of dataParts) {
483
484
  const events = dataPart.data?.events;
484
485
  if (!Array.isArray(events)) {
485
- console.warn("[XY] dataPart.data.events is not an array, skipping");
486
+ this.log("[XY] dataPart.data.events is not an array, skipping");
486
487
  continue;
487
488
  }
488
- console.log(`[XY] Processing ${events.length} events from data.events`);
489
+ this.log(`[XY] Processing ${events.length} events from data.events`);
489
490
  for (const item of events) {
490
491
  if (item.header?.name === "UploadExeResult" && item.payload?.intentName) {
491
492
  const dataEvent = {
@@ -493,15 +494,15 @@ export class XYWebSocketManager extends EventEmitter {
493
494
  outputs: item.payload.outputs || {},
494
495
  status: "success",
495
496
  };
496
- console.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
497
+ this.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
497
498
  this.emit("data-event", dataEvent);
498
499
  }
499
500
  else if (item.header?.namespace === "ClawAgent" && item.header?.name === "InvokeJarvisGUIAgentResponse") {
500
- console.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
501
+ this.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
501
502
  this.emit("gui-agent-response", item);
502
503
  }
503
504
  else if (item.header?.namespace === "Common" && item.header?.name === "Trigger") {
504
- console.log("[XY] Trigger event detected (wrapped format), emitting trigger-event with context");
505
+ this.log("[XY] Trigger event detected (wrapped format), emitting trigger-event with context");
505
506
  // 传递完整上下文:event、sessionId、taskId
506
507
  this.emit("trigger-event", {
507
508
  event: item,
@@ -510,7 +511,7 @@ export class XYWebSocketManager extends EventEmitter {
510
511
  });
511
512
  }
512
513
  else if (item.header?.namespace === "LoginTokenEvent" && item.header?.name === "ClawAutoLogin") {
513
- console.log("[XY] LoginTokenEvent.ClawAutoLogin detected (wrapped format), emitting login-token-event");
514
+ this.log("[XY] LoginTokenEvent.ClawAutoLogin detected (wrapped format), emitting login-token-event");
514
515
  this.emit("login-token-event", {
515
516
  event: item,
516
517
  });
@@ -520,28 +521,28 @@ export class XYWebSocketManager extends EventEmitter {
520
521
  }
521
522
  }
522
523
  catch (error) {
523
- console.error("[XY] Failed to process data message:", error);
524
+ this.error("[XY] Failed to process data message:", error);
524
525
  }
525
526
  return;
526
527
  }
527
528
  // Parse msgDetail as A2AJsonRpcRequest
528
529
  const a2aRequest = JSON.parse(inboundMsg.msgDetail);
529
- console.log(`[XY] Parsed A2A request, method: ${a2aRequest.method}`);
530
+ this.log(`[XY] Parsed A2A request, method: ${a2aRequest.method}`);
530
531
  const sessionId = inboundMsg.sessionId;
531
- console.log(`[XY] Session ID: ${sessionId}`);
532
+ this.log(`[XY] Session ID: ${sessionId}`);
532
533
  // Emit message event
533
- console.log("[XY] *** EMITTING message event (Wrapped path) ***");
534
+ this.log("[XY] *** EMITTING message event (Wrapped path) ***");
534
535
  this.emit("message", a2aRequest, sessionId);
535
536
  }
536
537
  catch (error) {
537
- console.error("[XY] Failed to parse message:", error);
538
+ this.error("[XY] Failed to parse message:", error);
538
539
  }
539
540
  }
540
541
  /**
541
542
  * Handle connection close.
542
543
  */
543
544
  handleClose(code, reason) {
544
- console.warn(`WebSocket disconnected: code=${code}, reason=${reason}`);
545
+ this.log(`WebSocket disconnected: code=${code}, reason=${reason}`);
545
546
  // Only process if this is the current connection
546
547
  if (!this.ws) {
547
548
  this.log("Ignoring close event for already cleaned connection");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.124-beta",
3
+ "version": "0.0.125-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",