@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 —
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
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
|
|
160
|
-
|
|
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
|
}
|
package/dist/src/websocket.js
CHANGED
|
@@ -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
|
-
|
|
302
|
+
this.log("[WS-SEND] Sending init message frame:", JSON.stringify(initMessage, null, 2));
|
|
302
303
|
this.ws.send(initMessageStr);
|
|
303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
411
|
+
this.log("[XY] dataPart.data.events is not an array, skipping");
|
|
411
412
|
continue;
|
|
412
413
|
}
|
|
413
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
469
|
+
this.log(`[XY] Message type: Wrapped, msgType: ${inboundMsg.msgType}`);
|
|
469
470
|
// Handle heartbeat responses
|
|
470
471
|
if (inboundMsg.msgType === "heartbeat") {
|
|
471
|
-
|
|
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
|
-
|
|
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
|
-
|
|
486
|
+
this.log("[XY] dataPart.data.events is not an array, skipping");
|
|
486
487
|
continue;
|
|
487
488
|
}
|
|
488
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
530
|
+
this.log(`[XY] Parsed A2A request, method: ${a2aRequest.method}`);
|
|
530
531
|
const sessionId = inboundMsg.sessionId;
|
|
531
|
-
|
|
532
|
+
this.log(`[XY] Session ID: ${sessionId}`);
|
|
532
533
|
// Emit message event
|
|
533
|
-
|
|
534
|
+
this.log("[XY] *** EMITTING message event (Wrapped path) ***");
|
|
534
535
|
this.emit("message", a2aRequest, sessionId);
|
|
535
536
|
}
|
|
536
537
|
catch (error) {
|
|
537
|
-
|
|
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
|
-
|
|
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");
|