@ynhcj/xiaoyi-channel 0.0.139-next → 0.0.140-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/formatter.js +17 -10
- package/dist/src/reply-dispatcher.js +37 -34
- package/dist/src/utils/logger.d.ts +8 -0
- package/dist/src/utils/logger.js +18 -8
- package/dist/src/websocket.js +29 -26
- package/package.json +1 -1
package/dist/src/formatter.js
CHANGED
|
@@ -42,6 +42,7 @@ function buildTextPreview(text) {
|
|
|
42
42
|
*/
|
|
43
43
|
export async function sendA2AResponse(params) {
|
|
44
44
|
const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage } = params;
|
|
45
|
+
const log = logger.withContext(sessionId, taskId);
|
|
45
46
|
// 审批桥接:将 OpenClaw 的审批提示翻译成用户友好的确认文案
|
|
46
47
|
const bridgedText = text === undefined ? text : rewriteOutboundApprovalText(sessionId, text);
|
|
47
48
|
// Build artifact update event
|
|
@@ -84,7 +85,7 @@ export async function sendA2AResponse(params) {
|
|
|
84
85
|
code: errorCode,
|
|
85
86
|
message: errorMessage ?? "任务执行异常,请重试",
|
|
86
87
|
};
|
|
87
|
-
|
|
88
|
+
log.log(`[A2A_RESPONSE] Including error code: ${errorCode}`);
|
|
88
89
|
}
|
|
89
90
|
// Send via WebSocket
|
|
90
91
|
const wsManager = getXYWebSocketManager(config);
|
|
@@ -97,9 +98,9 @@ export async function sendA2AResponse(params) {
|
|
|
97
98
|
};
|
|
98
99
|
// Log complete response body
|
|
99
100
|
const redactedText = redactSensitiveText(bridgedText ?? "");
|
|
100
|
-
|
|
101
|
+
log.log(`[A2A_RESPONSE] Sending artifact-update, append=${append}, final=${final}, text=${buildTextPreview(redactedText)}, files=${files?.length ?? 0}, sensitive=${containsSensitiveInfo(bridgedText ?? "")}`);
|
|
101
102
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
102
|
-
|
|
103
|
+
log.log(`[A2A_RESPONSE] Message sent successfully`);
|
|
103
104
|
}
|
|
104
105
|
/**
|
|
105
106
|
* Send an A2A artifact-update with reasoningText part.
|
|
@@ -108,6 +109,7 @@ export async function sendA2AResponse(params) {
|
|
|
108
109
|
*/
|
|
109
110
|
export async function sendReasoningTextUpdate(params) {
|
|
110
111
|
const { config, sessionId, taskId, messageId, text, append = true } = params;
|
|
112
|
+
const log = logger.withContext(sessionId, taskId);
|
|
111
113
|
// 审批桥接
|
|
112
114
|
const bridgedText = rewriteOutboundApprovalText(sessionId, text);
|
|
113
115
|
const artifact = {
|
|
@@ -153,6 +155,7 @@ export async function sendStatusUpdate(params) {
|
|
|
153
155
|
// fall back to closure-captured values
|
|
154
156
|
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
155
157
|
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
158
|
+
const log = logger.withContext(sessionId, currentTaskId);
|
|
156
159
|
// 审批桥接和脱敏
|
|
157
160
|
const bridgedText = rewriteOutboundApprovalText(sessionId, text);
|
|
158
161
|
const redactedText = redactSensitiveText(bridgedText);
|
|
@@ -191,7 +194,7 @@ export async function sendStatusUpdate(params) {
|
|
|
191
194
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
192
195
|
};
|
|
193
196
|
// Log complete response body
|
|
194
|
-
|
|
197
|
+
log.log(`[A2A_STATUS] Sending status-update, text="${redactedText}"`);
|
|
195
198
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
196
199
|
}
|
|
197
200
|
/**
|
|
@@ -203,6 +206,7 @@ export async function sendCommand(params) {
|
|
|
203
206
|
// fall back to closure-captured values
|
|
204
207
|
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
205
208
|
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
209
|
+
const log = logger.withContext(sessionId, currentTaskId);
|
|
206
210
|
// Build artifact update with command as data
|
|
207
211
|
// Wrap command in commands array as per protocol requirement
|
|
208
212
|
const artifact = {
|
|
@@ -241,15 +245,16 @@ export async function sendCommand(params) {
|
|
|
241
245
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
242
246
|
};
|
|
243
247
|
// Log complete response body
|
|
244
|
-
|
|
248
|
+
log.log(`[A2A_COMMAND] Sending command`);
|
|
245
249
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
246
|
-
|
|
250
|
+
log.log(`[A2A_COMMAND] Command sent successfully`);
|
|
247
251
|
}
|
|
248
252
|
/**
|
|
249
253
|
* Send a clearContext response.
|
|
250
254
|
*/
|
|
251
255
|
export async function sendClearContextResponse(params) {
|
|
252
256
|
const { config, sessionId, messageId } = params;
|
|
257
|
+
const log = logger.withContext(sessionId, "");
|
|
253
258
|
// Build JSON-RPC response for clearContext
|
|
254
259
|
const jsonRpcResponse = {
|
|
255
260
|
jsonrpc: "2.0",
|
|
@@ -275,13 +280,14 @@ export async function sendClearContextResponse(params) {
|
|
|
275
280
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
276
281
|
};
|
|
277
282
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
278
|
-
|
|
283
|
+
log.log(`[CLEAR_CONTEXT] Sent clearContext response`);
|
|
279
284
|
}
|
|
280
285
|
/**
|
|
281
286
|
* Send a tasks/cancel response.
|
|
282
287
|
*/
|
|
283
288
|
export async function sendTasksCancelResponse(params) {
|
|
284
289
|
const { config, sessionId, taskId, messageId } = params;
|
|
290
|
+
const log = logger.withContext(sessionId, taskId);
|
|
285
291
|
// Build JSON-RPC response for tasks/cancel
|
|
286
292
|
// Note: Using any to bypass type check as the response format differs from standard A2A types
|
|
287
293
|
const jsonRpcResponse = {
|
|
@@ -308,13 +314,14 @@ export async function sendTasksCancelResponse(params) {
|
|
|
308
314
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
309
315
|
};
|
|
310
316
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
311
|
-
|
|
317
|
+
log.log(`[TASKS_CANCEL] Sent tasks/cancel response`);
|
|
312
318
|
}
|
|
313
319
|
/**
|
|
314
320
|
* Send a Trigger response with pushData content.
|
|
315
321
|
*/
|
|
316
322
|
export async function sendTriggerResponse(params) {
|
|
317
323
|
const { config, sessionId, taskId, messageId, content } = params;
|
|
324
|
+
const log = logger.withContext(sessionId, taskId);
|
|
318
325
|
// 审批桥接和脱敏
|
|
319
326
|
const bridgedContent = rewriteOutboundApprovalText(sessionId, content);
|
|
320
327
|
const redactedContent = redactSensitiveText(bridgedContent);
|
|
@@ -354,7 +361,7 @@ export async function sendTriggerResponse(params) {
|
|
|
354
361
|
taskId,
|
|
355
362
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
356
363
|
};
|
|
357
|
-
|
|
364
|
+
log.log(`[TRIGGER_RESPONSE] Sending Trigger response, text=${buildTextPreview(redactedContent)}`);
|
|
358
365
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
359
|
-
|
|
366
|
+
log.log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
|
|
360
367
|
}
|
|
@@ -46,7 +46,10 @@ export async function cleanupStaleTempFiles(tempDir = "/tmp/xy_channel") {
|
|
|
46
46
|
*/
|
|
47
47
|
export function createXYReplyDispatcher(params) {
|
|
48
48
|
const { cfg, runtime, sessionId, taskId, messageId, accountId, steerState } = params;
|
|
49
|
-
logger
|
|
49
|
+
// Create a scoped logger that always uses this session's sessionId
|
|
50
|
+
// and dynamically resolves the latest taskId
|
|
51
|
+
const scopedLog = () => logger.withContext(sessionId, getActiveTaskId());
|
|
52
|
+
scopedLog().log(`[DISPATCHER-CREATE] Creating dispatcher`);
|
|
50
53
|
// 初始taskId和messageId(作为fallback)
|
|
51
54
|
const initialTaskId = taskId;
|
|
52
55
|
const initialMessageId = messageId;
|
|
@@ -76,12 +79,12 @@ export function createXYReplyDispatcher(params) {
|
|
|
76
79
|
* Start the status update interval
|
|
77
80
|
*/
|
|
78
81
|
const startStatusInterval = () => {
|
|
79
|
-
|
|
82
|
+
scopedLog().log(`[STATUS-INTERVAL] Starting interval`);
|
|
80
83
|
statusUpdateInterval = setInterval(() => {
|
|
81
84
|
// 🔑 使用动态taskId
|
|
82
85
|
const currentTaskId = getActiveTaskId();
|
|
83
86
|
const currentMessageId = getActiveMessageId();
|
|
84
|
-
|
|
87
|
+
scopedLog().log(`[STATUS-INTERVAL] Triggering status update, taskId=${currentTaskId}`);
|
|
85
88
|
void sendStatusUpdate({
|
|
86
89
|
config,
|
|
87
90
|
sessionId,
|
|
@@ -90,13 +93,13 @@ export function createXYReplyDispatcher(params) {
|
|
|
90
93
|
text: "任务正在处理中,请稍候~",
|
|
91
94
|
state: "working",
|
|
92
95
|
}).catch((err) => {
|
|
93
|
-
|
|
96
|
+
scopedLog().error(`Failed to send status update:`, err);
|
|
94
97
|
});
|
|
95
98
|
}, 30000); // 30 seconds
|
|
96
99
|
};
|
|
97
100
|
const stopStatusInterval = () => {
|
|
98
101
|
if (statusUpdateInterval) {
|
|
99
|
-
|
|
102
|
+
scopedLog().log(`[STATUS-INTERVAL] Stopping interval`);
|
|
100
103
|
clearInterval(statusUpdateInterval);
|
|
101
104
|
statusUpdateInterval = null;
|
|
102
105
|
}
|
|
@@ -107,26 +110,26 @@ export function createXYReplyDispatcher(params) {
|
|
|
107
110
|
humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, accountId),
|
|
108
111
|
onReplyStart: () => {
|
|
109
112
|
const currentTaskId = getActiveTaskId();
|
|
110
|
-
|
|
113
|
+
scopedLog().log(`[REPLY-START] Reply started, taskId=${currentTaskId}, steered=${steerState.steered}`);
|
|
111
114
|
},
|
|
112
115
|
deliver: async (payload, info) => {
|
|
113
116
|
// 🔑 steered dispatch不发送内容(让主dispatcher处理)
|
|
114
117
|
if (steerState.steered) {
|
|
115
|
-
|
|
118
|
+
scopedLog().log(`[DELIVER] Steered dispatch, skipping, kind=${info?.kind}`);
|
|
116
119
|
return;
|
|
117
120
|
}
|
|
118
121
|
const text = payload.text ?? "";
|
|
119
122
|
const currentTaskId = getActiveTaskId();
|
|
120
123
|
const currentMessageId = getActiveMessageId();
|
|
121
|
-
|
|
124
|
+
scopedLog().log(`[DELIVER] kind=${info?.kind}, text.length=${text.length}`);
|
|
122
125
|
try {
|
|
123
126
|
if (!text.trim()) {
|
|
124
|
-
|
|
127
|
+
scopedLog().log(`[DELIVER SKIP] Empty text, skipping`);
|
|
125
128
|
return;
|
|
126
129
|
}
|
|
127
130
|
accumulatedText += text;
|
|
128
131
|
hasSentResponse = true;
|
|
129
|
-
|
|
132
|
+
scopedLog().log(`[DELIVER] Accumulated text, length=${accumulatedText.length}`);
|
|
130
133
|
// 🔑 使用动态taskId发送reasoningText更新
|
|
131
134
|
await sendReasoningTextUpdate({
|
|
132
135
|
config,
|
|
@@ -135,10 +138,10 @@ export function createXYReplyDispatcher(params) {
|
|
|
135
138
|
messageId: currentMessageId,
|
|
136
139
|
text,
|
|
137
140
|
});
|
|
138
|
-
|
|
141
|
+
scopedLog().log(`[DELIVER] Sent deliver text as reasoningText update`);
|
|
139
142
|
}
|
|
140
143
|
catch (deliverError) {
|
|
141
|
-
|
|
144
|
+
scopedLog().error(`Failed to deliver message:`, deliverError);
|
|
142
145
|
}
|
|
143
146
|
},
|
|
144
147
|
onError: async (err, info) => {
|
|
@@ -146,7 +149,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
146
149
|
stopStatusInterval();
|
|
147
150
|
// 🔑 steered dispatcher不发送错误状态(让主dispatcher处理)
|
|
148
151
|
if (steerState.steered) {
|
|
149
|
-
|
|
152
|
+
scopedLog().log(`[ON-ERROR] Steered dispatch, skipping error response`);
|
|
150
153
|
return;
|
|
151
154
|
}
|
|
152
155
|
if (!hasSentResponse) {
|
|
@@ -163,23 +166,23 @@ export function createXYReplyDispatcher(params) {
|
|
|
163
166
|
});
|
|
164
167
|
}
|
|
165
168
|
catch (statusError) {
|
|
166
|
-
|
|
169
|
+
scopedLog().error(`Failed to send error status:`, statusError);
|
|
167
170
|
}
|
|
168
171
|
}
|
|
169
172
|
},
|
|
170
173
|
onIdle: async () => {
|
|
171
174
|
const currentTaskId = getActiveTaskId();
|
|
172
175
|
const currentMessageId = getActiveMessageId();
|
|
173
|
-
|
|
176
|
+
scopedLog().log(`[ON-IDLE] Reply idle, steered=${steerState.steered}, hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
|
|
174
177
|
// 🔑 steered dispatch不发送final响应(核心已注入到活跃 Pi run)
|
|
175
178
|
if (steerState.steered) {
|
|
176
|
-
|
|
179
|
+
scopedLog().log(`[ON-IDLE] Steered dispatch, skipping final response`);
|
|
177
180
|
stopStatusInterval();
|
|
178
181
|
return; // ← 直接返回,不发送任何东西!
|
|
179
182
|
}
|
|
180
183
|
// 正常模式(或未被steer的dispatch)
|
|
181
184
|
if (hasSentResponse && !finalSent) {
|
|
182
|
-
|
|
185
|
+
scopedLog().log(`[ON-IDLE] Sending accumulated text, length=${accumulatedText.length}`);
|
|
183
186
|
try {
|
|
184
187
|
// 🔑 使用动态taskId发送完成状态
|
|
185
188
|
await sendStatusUpdate({
|
|
@@ -190,7 +193,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
190
193
|
text: "任务处理已完成~",
|
|
191
194
|
state: "completed",
|
|
192
195
|
});
|
|
193
|
-
|
|
196
|
+
scopedLog().log(`[ON-IDLE] Sent completion status update`);
|
|
194
197
|
// 🔑 使用动态taskId发送最终响应
|
|
195
198
|
await sendA2AResponse({
|
|
196
199
|
config,
|
|
@@ -202,15 +205,15 @@ export function createXYReplyDispatcher(params) {
|
|
|
202
205
|
final: true,
|
|
203
206
|
});
|
|
204
207
|
finalSent = true;
|
|
205
|
-
|
|
208
|
+
scopedLog().log(`[ON-IDLE] Sent final response`);
|
|
206
209
|
}
|
|
207
210
|
catch (err) {
|
|
208
|
-
|
|
211
|
+
scopedLog().error(`[ON-IDLE] Failed to send final response:`, err);
|
|
209
212
|
}
|
|
210
213
|
}
|
|
211
214
|
else {
|
|
212
215
|
// 正常失败场景(非steered)
|
|
213
|
-
|
|
216
|
+
scopedLog().log(`[ON-IDLE] Skipping final message: hasSentResponse=${hasSentResponse}, finalSent=${finalSent}`);
|
|
214
217
|
try {
|
|
215
218
|
await sendStatusUpdate({
|
|
216
219
|
config,
|
|
@@ -220,7 +223,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
220
223
|
text: "任务处理中断了~",
|
|
221
224
|
state: "failed",
|
|
222
225
|
});
|
|
223
|
-
|
|
226
|
+
scopedLog().log(`[ON-IDLE] Sent failure status update`);
|
|
224
227
|
await sendA2AResponse({
|
|
225
228
|
config,
|
|
226
229
|
sessionId,
|
|
@@ -233,17 +236,17 @@ export function createXYReplyDispatcher(params) {
|
|
|
233
236
|
errorMessage: "任务执行异常,请重试",
|
|
234
237
|
});
|
|
235
238
|
finalSent = true;
|
|
236
|
-
|
|
239
|
+
scopedLog().log(`[ON-IDLE] Sent error response, code=99921111`);
|
|
237
240
|
}
|
|
238
241
|
catch (err) {
|
|
239
|
-
|
|
242
|
+
scopedLog().error(`[ON-IDLE] Failed to send error response:`, err);
|
|
240
243
|
}
|
|
241
244
|
}
|
|
242
245
|
stopStatusInterval();
|
|
243
246
|
},
|
|
244
247
|
onCleanup: () => {
|
|
245
248
|
const currentTaskId = getActiveTaskId();
|
|
246
|
-
|
|
249
|
+
scopedLog().log(`[ON-CLEANUP] Reply cleanup, steered=${steerState.steered}`);
|
|
247
250
|
},
|
|
248
251
|
});
|
|
249
252
|
return {
|
|
@@ -258,13 +261,13 @@ export function createXYReplyDispatcher(params) {
|
|
|
258
261
|
}
|
|
259
262
|
const currentTaskId = getActiveTaskId();
|
|
260
263
|
const currentMessageId = getActiveMessageId();
|
|
261
|
-
|
|
264
|
+
scopedLog().log(`[TOOL-START] Tool: ${name}, phase: ${phase}`);
|
|
262
265
|
if (phase === "start") {
|
|
263
266
|
const toolName = name || "unknown";
|
|
264
267
|
// call_device_tool 由自身 execute() 内部发送具体子工具名的状态更新
|
|
265
268
|
// get_xxx_tool_schema 是给 LLM 查 schema 用的,无需向用户展示
|
|
266
269
|
if (toolName === "call_device_tool" || toolName.endsWith("_tool_schema") || toolName === "huawei_id_tool") {
|
|
267
|
-
|
|
270
|
+
scopedLog().log(`[TOOL-START] Skipping generic status for ${toolName}`);
|
|
268
271
|
return;
|
|
269
272
|
}
|
|
270
273
|
try {
|
|
@@ -276,10 +279,10 @@ export function createXYReplyDispatcher(params) {
|
|
|
276
279
|
text: `正在使用工具: ${toolName}...`,
|
|
277
280
|
state: "working",
|
|
278
281
|
});
|
|
279
|
-
|
|
282
|
+
scopedLog().log(`[TOOL-START] Sent status update for tool start: ${toolName}`);
|
|
280
283
|
}
|
|
281
284
|
catch (err) {
|
|
282
|
-
|
|
285
|
+
scopedLog().error(`[TOOL-START] Failed to send tool start status:`, err);
|
|
283
286
|
}
|
|
284
287
|
}
|
|
285
288
|
},
|
|
@@ -292,7 +295,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
292
295
|
const currentMessageId = getActiveMessageId();
|
|
293
296
|
const text = payload.text ?? "";
|
|
294
297
|
const hasMedia = Boolean(payload.mediaUrl || (payload.mediaUrls?.length ?? 0) > 0);
|
|
295
|
-
|
|
298
|
+
scopedLog().log(`[TOOL-RESULT] Tool result, text.length: ${text.length}`);
|
|
296
299
|
try {
|
|
297
300
|
if (text.length > 0 || hasMedia) {
|
|
298
301
|
const resultText = text.length > 0 ? text : "工具执行完成";
|
|
@@ -304,11 +307,11 @@ export function createXYReplyDispatcher(params) {
|
|
|
304
307
|
text: resultText,
|
|
305
308
|
state: "working",
|
|
306
309
|
});
|
|
307
|
-
|
|
310
|
+
scopedLog().log(`[TOOL-RESULT] Sent tool result as status update`);
|
|
308
311
|
}
|
|
309
312
|
}
|
|
310
313
|
catch (err) {
|
|
311
|
-
|
|
314
|
+
scopedLog().error(`[TOOL-RESULT] Failed to send tool result status:`, err);
|
|
312
315
|
}
|
|
313
316
|
},
|
|
314
317
|
onReasoningStream: async (payload) => {
|
|
@@ -317,7 +320,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
317
320
|
return;
|
|
318
321
|
}
|
|
319
322
|
const text = payload.text ?? "";
|
|
320
|
-
|
|
323
|
+
scopedLog().log(`[REASONING-STREAM] Reasoning chunk received, text.length: ${text.length}`);
|
|
321
324
|
// Reasoning stream 目前被注释掉
|
|
322
325
|
// 如果需要可以启用
|
|
323
326
|
},
|
|
@@ -342,7 +345,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
342
345
|
}
|
|
343
346
|
}
|
|
344
347
|
catch (err) {
|
|
345
|
-
|
|
348
|
+
scopedLog().error(`[PARTIAL REPLY] Failed to send partial reply:`, err);
|
|
346
349
|
}
|
|
347
350
|
},
|
|
348
351
|
},
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
export interface ScopedLogger {
|
|
2
|
+
log(message: string, ...args: any[]): void;
|
|
3
|
+
warn(message: string, ...args: any[]): void;
|
|
4
|
+
error(message: string, ...args: any[]): void;
|
|
5
|
+
debug(message: string, ...args: any[]): void;
|
|
6
|
+
}
|
|
1
7
|
export declare const logger: {
|
|
2
8
|
log(message: string, ...args: any[]): void;
|
|
3
9
|
warn(message: string, ...args: any[]): void;
|
|
4
10
|
error(message: string, ...args: any[]): void;
|
|
5
11
|
debug(message: string, ...args: any[]): void;
|
|
12
|
+
/** Create a scoped logger with explicit sessionId/taskId (bypasses AsyncLocalStorage/globalThis). */
|
|
13
|
+
withContext(sessionId: string, taskId: string): ScopedLogger;
|
|
6
14
|
};
|
package/dist/src/utils/logger.js
CHANGED
|
@@ -93,14 +93,6 @@ function getSessionInfo() {
|
|
|
93
93
|
catch { }
|
|
94
94
|
return { sessionId: "", taskId: "" };
|
|
95
95
|
}
|
|
96
|
-
// ── Core write function ──
|
|
97
|
-
function writeLog(level, message, args) {
|
|
98
|
-
checkRotation();
|
|
99
|
-
const { sessionId, taskId } = getSessionInfo();
|
|
100
|
-
const timestamp = formatTimestampUTC8();
|
|
101
|
-
const msg = args.length ? formatMessage(message, args) : message;
|
|
102
|
-
dest.write(`| ${level} | ${timestamp} | ${sessionId} | ${taskId} | ${msg}\n`);
|
|
103
|
-
}
|
|
104
96
|
// ── Global error handlers (catch-all) ──
|
|
105
97
|
process.on("uncaughtException", (err) => {
|
|
106
98
|
writeLog("fatal", "Uncaught exception", [err]);
|
|
@@ -108,6 +100,15 @@ process.on("uncaughtException", (err) => {
|
|
|
108
100
|
process.on("unhandledRejection", (reason) => {
|
|
109
101
|
writeLog("error", "Unhandled rejection", [reason]);
|
|
110
102
|
});
|
|
103
|
+
// ── Core write function with optional explicit context ──
|
|
104
|
+
function writeLog(level, message, args, explicitSid, explicitTid) {
|
|
105
|
+
checkRotation();
|
|
106
|
+
const sessionId = explicitSid !== undefined ? explicitSid : getSessionInfo().sessionId;
|
|
107
|
+
const taskId = explicitTid !== undefined ? explicitTid : getSessionInfo().taskId;
|
|
108
|
+
const timestamp = formatTimestampUTC8();
|
|
109
|
+
const msg = args.length ? formatMessage(message, args) : message;
|
|
110
|
+
dest.write(`|${level}|${timestamp}|${sessionId}|${taskId}|${msg}\n`);
|
|
111
|
+
}
|
|
111
112
|
// ── Exported logger ──
|
|
112
113
|
export const logger = {
|
|
113
114
|
log(message, ...args) {
|
|
@@ -122,6 +123,15 @@ export const logger = {
|
|
|
122
123
|
debug(message, ...args) {
|
|
123
124
|
writeLog("debug", message, args);
|
|
124
125
|
},
|
|
126
|
+
/** Create a scoped logger with explicit sessionId/taskId (bypasses AsyncLocalStorage/globalThis). */
|
|
127
|
+
withContext(sessionId, taskId) {
|
|
128
|
+
return {
|
|
129
|
+
log(message, ...args) { writeLog("info", message, args, sessionId, taskId); },
|
|
130
|
+
warn(message, ...args) { writeLog("warn", message, args, sessionId, taskId); },
|
|
131
|
+
error(message, ...args) { writeLog("error", message, args, sessionId, taskId); },
|
|
132
|
+
debug(message, ...args) { writeLog("debug", message, args, sessionId, taskId); },
|
|
133
|
+
};
|
|
134
|
+
},
|
|
125
135
|
};
|
|
126
136
|
function formatMessage(message, args) {
|
|
127
137
|
if (!args.length)
|
package/dist/src/websocket.js
CHANGED
|
@@ -365,8 +365,14 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
365
365
|
handleMessage(data) {
|
|
366
366
|
try {
|
|
367
367
|
const messageStr = data.toString();
|
|
368
|
-
this.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} characters`);
|
|
369
368
|
const parsed = JSON.parse(messageStr);
|
|
369
|
+
// Extract sessionId/taskId early for scoped logging
|
|
370
|
+
const sessionId = parsed.params?.sessionId || parsed.sessionId;
|
|
371
|
+
const taskId = parsed.params?.id || parsed.taskId || "";
|
|
372
|
+
const log = sessionId
|
|
373
|
+
? logger.withContext(sessionId, taskId)
|
|
374
|
+
: { log: (msg, ...args) => logger.log(msg, ...args) };
|
|
375
|
+
log.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} characters`);
|
|
370
376
|
// 提取并打印消息内容(只显示 text,data 只打印提示)
|
|
371
377
|
const parts = parsed.params?.message?.parts;
|
|
372
378
|
if (parts && Array.isArray(parts) && parts.length > 0) {
|
|
@@ -391,7 +397,7 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
391
397
|
// 如果长度 > 8,显示前5个 + *** + 后5个
|
|
392
398
|
maskedText = `${textContents.slice(0, 5)}***${textContents.slice(-5)}`;
|
|
393
399
|
}
|
|
394
|
-
|
|
400
|
+
log.log(`[WS-RECV] Text: ${maskedText}`);
|
|
395
401
|
}
|
|
396
402
|
}
|
|
397
403
|
}
|
|
@@ -399,7 +405,6 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
399
405
|
if (parsed.jsonrpc === "2.0") {
|
|
400
406
|
const a2aRequest = parsed;
|
|
401
407
|
// Extract sessionId from params
|
|
402
|
-
const sessionId = a2aRequest.params?.sessionId;
|
|
403
408
|
if (!sessionId) {
|
|
404
409
|
this.error("[XY] Message missing sessionId");
|
|
405
410
|
return;
|
|
@@ -412,10 +417,10 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
412
417
|
for (const dataPart of dataParts) {
|
|
413
418
|
const events = dataPart.data?.events;
|
|
414
419
|
if (!Array.isArray(events)) {
|
|
415
|
-
|
|
420
|
+
log.log("[XY] dataPart.data.events is not an array, skipping");
|
|
416
421
|
continue;
|
|
417
422
|
}
|
|
418
|
-
|
|
423
|
+
log.log(`[XY] Processing ${events.length} events from data.events`);
|
|
419
424
|
for (const item of events) {
|
|
420
425
|
if (item.header?.name === "UploadExeResult" && item.payload?.intentName) {
|
|
421
426
|
const dataEvent = {
|
|
@@ -423,15 +428,15 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
423
428
|
outputs: item.payload.outputs || {},
|
|
424
429
|
status: "success",
|
|
425
430
|
};
|
|
426
|
-
|
|
431
|
+
log.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
|
|
427
432
|
this.emit("data-event", dataEvent);
|
|
428
433
|
}
|
|
429
434
|
else if (item.header?.namespace === "ClawAgent" && item.header?.name === "InvokeJarvisGUIAgentResponse") {
|
|
430
|
-
|
|
435
|
+
log.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
|
|
431
436
|
this.emit("gui-agent-response", item);
|
|
432
437
|
}
|
|
433
438
|
else if (item.header?.namespace === "Common" && item.header?.name === "Trigger") {
|
|
434
|
-
|
|
439
|
+
log.log("[XY] Trigger event detected, emitting trigger-event with context");
|
|
435
440
|
// 传递完整上下文:event、sessionId、taskId
|
|
436
441
|
this.emit("trigger-event", {
|
|
437
442
|
event: item,
|
|
@@ -440,13 +445,13 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
440
445
|
});
|
|
441
446
|
}
|
|
442
447
|
else if (item.header?.namespace === "AgentEvent" && item.header?.name === "ClawSelfEvolutionState") {
|
|
443
|
-
|
|
448
|
+
log.log("[XY] ClawSelfEvolutionState event detected, emitting self-evolution-event");
|
|
444
449
|
this.emit("self-evolution-event", {
|
|
445
450
|
event: item,
|
|
446
451
|
});
|
|
447
452
|
}
|
|
448
453
|
else if (item.header?.namespace === "AgentEvent" && item.header?.name === "ClawSelfEvolutionStateGet") {
|
|
449
|
-
|
|
454
|
+
log.log("[XY] ClawSelfEvolutionStateGet event detected, emitting self-evolution-state-get-event");
|
|
450
455
|
this.emit("self-evolution-state-get-event", {
|
|
451
456
|
event: item,
|
|
452
457
|
sessionId: sessionId,
|
|
@@ -455,13 +460,13 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
455
460
|
});
|
|
456
461
|
}
|
|
457
462
|
else if (item.header?.namespace === "LoginTokenEvent" && item.header?.name === "ClawAutoLogin") {
|
|
458
|
-
|
|
463
|
+
log.log("[XY] LoginTokenEvent.ClawAutoLogin detected, emitting login-token-event");
|
|
459
464
|
this.emit("login-token-event", {
|
|
460
465
|
event: item,
|
|
461
466
|
});
|
|
462
467
|
}
|
|
463
468
|
else if (item.header?.namespace === "System" && item.header?.name === "ExecuteAgentAsSkillResponse") {
|
|
464
|
-
|
|
469
|
+
log.log("[XY] ExecuteAgentAsSkillResponse detected, emitting agent-as-skill-response");
|
|
465
470
|
this.emit("agent-as-skill-response", item);
|
|
466
471
|
}
|
|
467
472
|
}
|
|
@@ -474,16 +479,16 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
474
479
|
}
|
|
475
480
|
// Wrapped format (InboundWebSocketMessage)
|
|
476
481
|
const inboundMsg = parsed;
|
|
477
|
-
|
|
482
|
+
log.log(`[XY] Message type: Wrapped, msgType: ${inboundMsg.msgType}`);
|
|
478
483
|
// Handle heartbeat responses
|
|
479
484
|
if (inboundMsg.msgType === "heartbeat") {
|
|
480
|
-
|
|
485
|
+
log.log("[XY] Received heartbeat response");
|
|
481
486
|
this.onHealthEvent?.();
|
|
482
487
|
return;
|
|
483
488
|
}
|
|
484
489
|
// Handle data messages
|
|
485
490
|
if (inboundMsg.msgType === "data") {
|
|
486
|
-
|
|
491
|
+
log.log("[XY] Processing data message");
|
|
487
492
|
try {
|
|
488
493
|
const a2aRequest = JSON.parse(inboundMsg.msgDetail);
|
|
489
494
|
const dataParts = a2aRequest.params?.message?.parts?.filter((p) => p.kind === "data");
|
|
@@ -491,10 +496,10 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
491
496
|
for (const dataPart of dataParts) {
|
|
492
497
|
const events = dataPart.data?.events;
|
|
493
498
|
if (!Array.isArray(events)) {
|
|
494
|
-
|
|
499
|
+
log.log("[XY] dataPart.data.events is not an array, skipping");
|
|
495
500
|
continue;
|
|
496
501
|
}
|
|
497
|
-
|
|
502
|
+
log.log(`[XY] Processing ${events.length} events from data.events`);
|
|
498
503
|
for (const item of events) {
|
|
499
504
|
if (item.header?.name === "UploadExeResult" && item.payload?.intentName) {
|
|
500
505
|
const dataEvent = {
|
|
@@ -502,15 +507,15 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
502
507
|
outputs: item.payload.outputs || {},
|
|
503
508
|
status: "success",
|
|
504
509
|
};
|
|
505
|
-
|
|
510
|
+
log.log(`[XY] Emitting data-event, intentName: ${item.payload.intentName}, size: ${JSON.stringify(dataEvent).length} bytes`);
|
|
506
511
|
this.emit("data-event", dataEvent);
|
|
507
512
|
}
|
|
508
513
|
else if (item.header?.namespace === "ClawAgent" && item.header?.name === "InvokeJarvisGUIAgentResponse") {
|
|
509
|
-
|
|
514
|
+
log.log(`[XY] Emitting gui-agent-response, size: ${JSON.stringify(item).length} bytes`);
|
|
510
515
|
this.emit("gui-agent-response", item);
|
|
511
516
|
}
|
|
512
517
|
else if (item.header?.namespace === "Common" && item.header?.name === "Trigger") {
|
|
513
|
-
|
|
518
|
+
log.log("[XY] Trigger event detected (wrapped format), emitting trigger-event with context");
|
|
514
519
|
// 传递完整上下文:event、sessionId、taskId
|
|
515
520
|
this.emit("trigger-event", {
|
|
516
521
|
event: item,
|
|
@@ -519,13 +524,13 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
519
524
|
});
|
|
520
525
|
}
|
|
521
526
|
else if (item.header?.namespace === "LoginTokenEvent" && item.header?.name === "ClawAutoLogin") {
|
|
522
|
-
|
|
527
|
+
log.log("[XY] LoginTokenEvent.ClawAutoLogin detected (wrapped format), emitting login-token-event");
|
|
523
528
|
this.emit("login-token-event", {
|
|
524
529
|
event: item,
|
|
525
530
|
});
|
|
526
531
|
}
|
|
527
532
|
else if (item.header?.namespace === "System" && item.header?.name === "ExecuteAgentAsSkillResponse") {
|
|
528
|
-
|
|
533
|
+
log.log("[XY] ExecuteAgentAsSkillResponse detected (wrapped format), emitting agent-as-skill-response");
|
|
529
534
|
this.emit("agent-as-skill-response", item);
|
|
530
535
|
}
|
|
531
536
|
}
|
|
@@ -539,11 +544,9 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
539
544
|
}
|
|
540
545
|
// Parse msgDetail as A2AJsonRpcRequest
|
|
541
546
|
const a2aRequest = JSON.parse(inboundMsg.msgDetail);
|
|
542
|
-
|
|
543
|
-
const sessionId = inboundMsg.sessionId;
|
|
544
|
-
this.log(`[XY] Session ID: ${sessionId}`);
|
|
547
|
+
log.log(`[XY] Parsed A2A request, method: ${a2aRequest.method}`);
|
|
545
548
|
// Emit message event
|
|
546
|
-
|
|
549
|
+
log.log("[XY] Emitting message event (Wrapped path)");
|
|
547
550
|
this.emit("message", a2aRequest, sessionId);
|
|
548
551
|
}
|
|
549
552
|
catch (error) {
|