@ynhcj/xiaoyi-channel 0.0.141-next → 0.0.142-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/bot.js +62 -50
- package/dist/src/reply-dispatcher.js +4 -8
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -35,7 +35,8 @@ export async function handleXYMessage(params) {
|
|
|
35
35
|
if (!sessionId) {
|
|
36
36
|
throw new Error("clearContext request missing sessionId in params");
|
|
37
37
|
}
|
|
38
|
-
logger.
|
|
38
|
+
const log = logger.withContext(sessionId, "");
|
|
39
|
+
log.log(`[BOT] Clear context request`);
|
|
39
40
|
const config = resolveXYConfig(cfg);
|
|
40
41
|
await sendClearContextResponse({
|
|
41
42
|
config,
|
|
@@ -51,7 +52,8 @@ export async function handleXYMessage(params) {
|
|
|
51
52
|
if (!sessionId) {
|
|
52
53
|
throw new Error("tasks/cancel request missing sessionId in params");
|
|
53
54
|
}
|
|
54
|
-
logger.
|
|
55
|
+
const log = logger.withContext(sessionId, taskId);
|
|
56
|
+
log.log(`[BOT] Tasks cancel request`);
|
|
55
57
|
const config = resolveXYConfig(cfg);
|
|
56
58
|
await sendTasksCancelResponse({
|
|
57
59
|
config,
|
|
@@ -63,19 +65,21 @@ export async function handleXYMessage(params) {
|
|
|
63
65
|
}
|
|
64
66
|
// Parse the A2A message (for regular messages)
|
|
65
67
|
const parsed = parseA2AMessage(message);
|
|
68
|
+
// Scoped logger for this session — avoids concurrent session log mixing
|
|
69
|
+
const log = logger.withContext(parsed.sessionId, parsed.taskId);
|
|
66
70
|
// ========== 检测 Trigger 消息 ==========
|
|
67
71
|
// 如果消息中包含 Trigger 事件数据,直接返回 pushData 内容,不走正常流程
|
|
68
72
|
const triggerData = extractTriggerData(parsed.parts);
|
|
69
73
|
if (triggerData) {
|
|
70
|
-
|
|
74
|
+
log.log(`[BOT] Detected Trigger message, pushDataId=${triggerData.pushDataId}`);
|
|
71
75
|
try {
|
|
72
76
|
// 读取 pushData
|
|
73
77
|
const pushDataItem = await getPushDataById(triggerData.pushDataId);
|
|
74
78
|
if (!pushDataItem) {
|
|
75
|
-
|
|
79
|
+
log.error(`[BOT] pushData not found for ID: ${triggerData.pushDataId}`);
|
|
76
80
|
return;
|
|
77
81
|
}
|
|
78
|
-
|
|
82
|
+
log.log(`[BOT] Found pushData, sending direct response, pushDataId=${pushDataItem.pushDataId}`);
|
|
79
83
|
const config = resolveXYConfig(cfg);
|
|
80
84
|
// 直接发送响应(final=true,不走 openclaw 流程)
|
|
81
85
|
await sendA2AResponse({
|
|
@@ -87,11 +91,11 @@ export async function handleXYMessage(params) {
|
|
|
87
91
|
append: false,
|
|
88
92
|
final: true,
|
|
89
93
|
});
|
|
90
|
-
|
|
94
|
+
log.log(`[BOT] Trigger response sent successfully`);
|
|
91
95
|
return; // 提前返回,不继续处理
|
|
92
96
|
}
|
|
93
97
|
catch (err) {
|
|
94
|
-
|
|
98
|
+
log.error(`[BOT] Failed to handle Trigger message:`, err);
|
|
95
99
|
return;
|
|
96
100
|
}
|
|
97
101
|
}
|
|
@@ -100,7 +104,7 @@ export async function handleXYMessage(params) {
|
|
|
100
104
|
const isUpdate = hasActiveTask(parsed.sessionId);
|
|
101
105
|
const skipReg = params.skipRegistration === true;
|
|
102
106
|
if (isUpdate) {
|
|
103
|
-
|
|
107
|
+
log.log(`[BOT] STEER MODE - Second message detected, new taskId=${parsed.taskId}`);
|
|
104
108
|
}
|
|
105
109
|
// Steer injections skip taskId registration to avoid overwriting the active taskId
|
|
106
110
|
if (!skipReg) {
|
|
@@ -108,28 +112,28 @@ export async function handleXYMessage(params) {
|
|
|
108
112
|
// Extract and update push_id if present
|
|
109
113
|
const pushId = extractPushId(parsed.parts);
|
|
110
114
|
if (pushId) {
|
|
111
|
-
|
|
115
|
+
log.log(`[BOT] Extracted push_id from user message`);
|
|
112
116
|
configManager.updatePushId(parsed.sessionId, pushId);
|
|
113
117
|
// 持久化 pushId 到本地文件(异步,不阻塞主流程)
|
|
114
118
|
addPushId(pushId).catch((err) => {
|
|
115
|
-
|
|
119
|
+
log.error(`[BOT] Failed to persist pushId:`, err);
|
|
116
120
|
});
|
|
117
121
|
}
|
|
118
122
|
else {
|
|
119
|
-
|
|
123
|
+
log.log(`[BOT] No push_id found in message, using config default`);
|
|
120
124
|
}
|
|
121
125
|
// 保存 runtime 信息到 .xiaoyiruntime 文件(异步,不阻塞主流程)
|
|
122
126
|
saveRuntimeInfo(webSocketSessionId || parsed.sessionId, // SESSION_ID (WebSocket 层级,如果没有则 fallback)
|
|
123
127
|
parsed.sessionId, // CONVERSATION_ID (param 里的 sessionId)
|
|
124
128
|
parsed.taskId // TASK_ID (param.id)
|
|
125
129
|
).catch((err) => {
|
|
126
|
-
|
|
130
|
+
log.error(`[BOT] Failed to save runtime info:`, err);
|
|
127
131
|
});
|
|
128
132
|
}
|
|
129
133
|
// Extract deviceType if present (always parse — used in ctxPayload.MessageSid)
|
|
130
134
|
const deviceType = extractDeviceType(parsed.parts);
|
|
131
135
|
if (deviceType) {
|
|
132
|
-
|
|
136
|
+
log.log(`[BOT] Extracted deviceType: ${deviceType}`);
|
|
133
137
|
}
|
|
134
138
|
// Resolve configuration (needed for status updates)
|
|
135
139
|
const config = resolveXYConfig(cfg);
|
|
@@ -145,7 +149,7 @@ export async function handleXYMessage(params) {
|
|
|
145
149
|
id: parsed.sessionId, // ✅ Use sessionId to share context within the same conversation session
|
|
146
150
|
},
|
|
147
151
|
});
|
|
148
|
-
|
|
152
|
+
log.log(`[BOT] Resolved route, sessionKey=${route.sessionKey}`);
|
|
149
153
|
// Steer injections skip session registration to avoid refCount leaks
|
|
150
154
|
if (!skipReg) {
|
|
151
155
|
registerSession(route.sessionKey, {
|
|
@@ -157,7 +161,7 @@ export async function handleXYMessage(params) {
|
|
|
157
161
|
deviceType,
|
|
158
162
|
});
|
|
159
163
|
// 🔑 发送初始状态更新
|
|
160
|
-
|
|
164
|
+
log.log(`[BOT] Sending initial status update`);
|
|
161
165
|
void sendStatusUpdate({
|
|
162
166
|
config,
|
|
163
167
|
sessionId: parsed.sessionId,
|
|
@@ -166,7 +170,7 @@ export async function handleXYMessage(params) {
|
|
|
166
170
|
text: "任务正在处理中,请稍候~",
|
|
167
171
|
state: "working",
|
|
168
172
|
}).catch((err) => {
|
|
169
|
-
|
|
173
|
+
log.error(`Failed to send initial status update:`, err);
|
|
170
174
|
});
|
|
171
175
|
}
|
|
172
176
|
// Extract text and files from parts
|
|
@@ -178,18 +182,18 @@ export async function handleXYMessage(params) {
|
|
|
178
182
|
const selfEvolutionEnabled = await selfEvolutionManager.isEnabled();
|
|
179
183
|
if (selfEvolutionEnabled && shouldNudgeForSelfEvolutionKeyword(textForAgent)) {
|
|
180
184
|
const shouldNudge = toolCallNudgeManager.tryMarkKeywordNudge(route.sessionKey);
|
|
181
|
-
|
|
185
|
+
log.log(`[SELF_EVOLUTION] Keyword check hit during inbound build: sessionKey=${route.sessionKey}, shouldNudge=${shouldNudge}`);
|
|
182
186
|
if (shouldNudge) {
|
|
183
187
|
const augmented = appendSelfEvolutionKeywordNudge(textForAgent);
|
|
184
188
|
textForAgent = augmented.text;
|
|
185
189
|
if (augmented.appended) {
|
|
186
|
-
|
|
190
|
+
log.log(`[SELF_EVOLUTION] Keyword-triggered inline nudge appended: sessionKey=${route.sessionKey}`);
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
194
|
}
|
|
191
195
|
catch (selfEvolutionError) {
|
|
192
|
-
|
|
196
|
+
log.error(`[SELF_EVOLUTION] Failed to append inline keyword nudge: ${String(selfEvolutionError)}`);
|
|
193
197
|
}
|
|
194
198
|
}
|
|
195
199
|
// 🔑 Steer消息: 跳过旧路径直接进入 streaming-signal 队列
|
|
@@ -204,10 +208,10 @@ export async function handleXYMessage(params) {
|
|
|
204
208
|
const steerDownloadedFiles = await downloadFilesFromParts(steerFileParts);
|
|
205
209
|
const steerMediaPayload = buildXYMediaPayload(steerDownloadedFiles);
|
|
206
210
|
if (steerFileParts.length > 0) {
|
|
207
|
-
|
|
211
|
+
log.log(`[BOT] Steer message with ${steerFileParts.length} file(s), enqueuing to streaming-signal queue`);
|
|
208
212
|
}
|
|
209
213
|
else {
|
|
210
|
-
|
|
214
|
+
log.log(`[BOT] Steer message — enqueuing to streaming-signal queue`);
|
|
211
215
|
}
|
|
212
216
|
await enqueueSteer({
|
|
213
217
|
sessionId: parsed.sessionId,
|
|
@@ -220,7 +224,7 @@ export async function handleXYMessage(params) {
|
|
|
220
224
|
route,
|
|
221
225
|
deviceType,
|
|
222
226
|
});
|
|
223
|
-
|
|
227
|
+
log.log(`[BOT] Steer queue completed`);
|
|
224
228
|
return;
|
|
225
229
|
}
|
|
226
230
|
// ── First message (non-steer) path below ──────────────────────
|
|
@@ -232,7 +236,7 @@ export async function handleXYMessage(params) {
|
|
|
232
236
|
if (!skipReg) {
|
|
233
237
|
const fileParts = extractFileParts(parsed.parts);
|
|
234
238
|
const downloadedFiles = await downloadFilesFromParts(fileParts);
|
|
235
|
-
|
|
239
|
+
log.log(`[BOT] Downloaded ${downloadedFiles.length} file(s)`);
|
|
236
240
|
mediaPayload = buildXYMediaPayload(downloadedFiles);
|
|
237
241
|
}
|
|
238
242
|
// Resolve envelope format options (following feishu pattern)
|
|
@@ -278,7 +282,7 @@ export async function handleXYMessage(params) {
|
|
|
278
282
|
// 🔑 Streaming 信号已在上方创建(在文件下载之前)
|
|
279
283
|
const steerState = { steered: false };
|
|
280
284
|
// 🔑 创建dispatcher
|
|
281
|
-
|
|
285
|
+
log.log(`[BOT-DISPATCHER] Creating reply dispatcher`);
|
|
282
286
|
const { dispatcher, replyOptions, markDispatchIdle, startStatusInterval } = createXYReplyDispatcher({
|
|
283
287
|
cfg,
|
|
284
288
|
runtime,
|
|
@@ -301,20 +305,20 @@ export async function handleXYMessage(params) {
|
|
|
301
305
|
agentId: route.accountId,
|
|
302
306
|
deviceType,
|
|
303
307
|
};
|
|
304
|
-
|
|
308
|
+
log.log(`[BOT-DISPATCH] withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
|
|
305
309
|
await core.channel.reply.withReplyDispatcher({
|
|
306
310
|
dispatcher,
|
|
307
311
|
onSettled: () => {
|
|
308
|
-
|
|
312
|
+
log.log(`[BOT] onSettled, steered=${steerState.steered}`);
|
|
309
313
|
// 🔑 When steered, skip heavy cleanup — the first message's dispatcher is still running
|
|
310
314
|
if (steerState.steered) {
|
|
311
|
-
|
|
315
|
+
log.log(`[BOT] Steered dispatch settled, skipping cleanup`);
|
|
312
316
|
return;
|
|
313
317
|
}
|
|
314
318
|
streamingSignals.delete(parsed.sessionId);
|
|
315
319
|
decrementTaskIdRef(parsed.sessionId);
|
|
316
320
|
unregisterSession(route.sessionKey);
|
|
317
|
-
|
|
321
|
+
log.log(`[BOT] Cleanup completed`);
|
|
318
322
|
},
|
|
319
323
|
run: () => {
|
|
320
324
|
// 🔐 Use AsyncLocalStorage to provide session context to tools.
|
|
@@ -323,7 +327,7 @@ export async function handleXYMessage(params) {
|
|
|
323
327
|
// signal init complete to release the global dispatch gate
|
|
324
328
|
// for the next session.
|
|
325
329
|
const dispatchPromise = runWithSessionContext(sessionContext, async () => {
|
|
326
|
-
|
|
330
|
+
log.log(`[BOT-DISPATCH] dispatchReplyFromConfig starting, body.length=${ctxPayload.Body?.length ?? 0}`);
|
|
327
331
|
try {
|
|
328
332
|
const result = await core.channel.reply.dispatchReplyFromConfig({
|
|
329
333
|
ctx: ctxPayload,
|
|
@@ -331,11 +335,11 @@ export async function handleXYMessage(params) {
|
|
|
331
335
|
dispatcher,
|
|
332
336
|
replyOptions,
|
|
333
337
|
});
|
|
334
|
-
|
|
338
|
+
log.log(`[BOT-DISPATCH] dispatchReplyFromConfig returned, result=${JSON.stringify(result)}`);
|
|
335
339
|
return result;
|
|
336
340
|
}
|
|
337
341
|
catch (dispatchErr) {
|
|
338
|
-
|
|
342
|
+
log.error(`[BOT-DISPATCH] dispatchReplyFromConfig threw: ${dispatchErr instanceof Error ? `${dispatchErr.name}: ${dispatchErr.message}` : String(dispatchErr)}`, dispatchErr instanceof Error ? dispatchErr.stack?.slice(0, 500) : undefined);
|
|
339
343
|
throw dispatchErr;
|
|
340
344
|
}
|
|
341
345
|
});
|
|
@@ -344,19 +348,23 @@ export async function handleXYMessage(params) {
|
|
|
344
348
|
return dispatchPromise;
|
|
345
349
|
},
|
|
346
350
|
});
|
|
347
|
-
|
|
351
|
+
log.log(`[BOT] Dispatcher completed`);
|
|
348
352
|
}
|
|
349
353
|
catch (err) {
|
|
350
354
|
// ✅ Only log error, don't re-throw to prevent gateway restart
|
|
351
|
-
|
|
355
|
+
// Note: if error occurs before parseA2AMessage, `log` may not be defined yet
|
|
356
|
+
const errSessionId = message.params?.sessionId || "";
|
|
357
|
+
const errTaskId = message.params?.id || message.id || "";
|
|
358
|
+
const errLog = logger.withContext(errSessionId, errTaskId);
|
|
359
|
+
errLog.error("Failed to handle XY message:", err);
|
|
352
360
|
runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
|
|
353
|
-
|
|
361
|
+
errLog.log(`[BOT] Error occurred, attempting cleanup`);
|
|
354
362
|
// 🔑 错误时也要清理taskId和session
|
|
355
363
|
try {
|
|
356
364
|
const params = message.params;
|
|
357
365
|
const sessionId = params?.sessionId;
|
|
358
366
|
if (sessionId) {
|
|
359
|
-
|
|
367
|
+
errLog.log(`[BOT] Cleaning up after error`);
|
|
360
368
|
// 清理 taskId
|
|
361
369
|
decrementTaskIdRef(sessionId);
|
|
362
370
|
// 清理 session
|
|
@@ -371,11 +379,11 @@ export async function handleXYMessage(params) {
|
|
|
371
379
|
},
|
|
372
380
|
});
|
|
373
381
|
unregisterSession(route.sessionKey);
|
|
374
|
-
|
|
382
|
+
errLog.log(`[BOT] Cleanup completed after error`);
|
|
375
383
|
}
|
|
376
384
|
}
|
|
377
385
|
catch (cleanupErr) {
|
|
378
|
-
|
|
386
|
+
errLog.log(`[BOT] Cleanup failed:`, cleanupErr);
|
|
379
387
|
// Ignore cleanup errors
|
|
380
388
|
}
|
|
381
389
|
// ❌ Don't re-throw: message processing error should not affect gateway stability
|
|
@@ -412,20 +420,22 @@ const steerQueues = _g.__xySteerQueues;
|
|
|
412
420
|
* 这是模型 API 被调用的精确时刻,此时 isStreaming 一定为 true。
|
|
413
421
|
*/
|
|
414
422
|
export function notifyModelStreaming(sessionId) {
|
|
423
|
+
const log = logger.withContext(sessionId, "");
|
|
415
424
|
const signal = streamingSignals.get(sessionId);
|
|
416
425
|
if (signal) {
|
|
417
426
|
// 不删除 signal——后续 steer 需要靠它判断模型已在 streaming。
|
|
418
427
|
// 清理由第一条消息的 onSettled 兜底。
|
|
419
428
|
signal.notify();
|
|
420
|
-
|
|
429
|
+
log.log(`[STEER-QUEUE] Model streaming signal fired`);
|
|
421
430
|
}
|
|
422
431
|
}
|
|
423
432
|
function createStreamingSignal(sessionId) {
|
|
433
|
+
const log = logger.withContext(sessionId, "");
|
|
424
434
|
let resolve;
|
|
425
435
|
const promise = new Promise(r => { resolve = r; });
|
|
426
436
|
const signal = { promise, notify: resolve };
|
|
427
437
|
streamingSignals.set(sessionId, signal);
|
|
428
|
-
|
|
438
|
+
log.log(`[STEER-QUEUE] Streaming signal created`);
|
|
429
439
|
return signal;
|
|
430
440
|
}
|
|
431
441
|
/**
|
|
@@ -435,13 +445,14 @@ function createStreamingSignal(sessionId) {
|
|
|
435
445
|
*/
|
|
436
446
|
function enqueueSteer(params) {
|
|
437
447
|
const { sessionId } = params;
|
|
448
|
+
const log = logger.withContext(sessionId, params.parsed.taskId);
|
|
438
449
|
// 取出当前队列尾部(或 undefined),然后链上新的 Promise
|
|
439
450
|
const prev = steerQueues.get(sessionId);
|
|
440
451
|
const next = (prev ?? Promise.resolve()).then(() => dispatchSteerWhenReady(params));
|
|
441
452
|
steerQueues.set(sessionId, next);
|
|
442
453
|
// 链条结束后清理
|
|
443
454
|
next.catch((err) => {
|
|
444
|
-
|
|
455
|
+
log.error(`[STEER-QUEUE] Steer chain failed: ${String(err)}`);
|
|
445
456
|
}).finally(() => {
|
|
446
457
|
if (steerQueues.get(sessionId) === next) {
|
|
447
458
|
steerQueues.delete(sessionId);
|
|
@@ -451,37 +462,38 @@ function enqueueSteer(params) {
|
|
|
451
462
|
}
|
|
452
463
|
async function dispatchSteerWhenReady(params) {
|
|
453
464
|
const { sessionId, sessionKey, steerText } = params;
|
|
465
|
+
const log = logger.withContext(sessionId, params.parsed.taskId);
|
|
454
466
|
// 1. 等待第一条消息开始 streaming
|
|
455
467
|
// signal 可能尚未创建(第一条消息还在文件下载等耗时操作中),
|
|
456
468
|
// 轮询等待直到 signal 出现,最長等待 ~5 秒。
|
|
457
469
|
let signal = streamingSignals.get(sessionId);
|
|
458
470
|
if (!signal) {
|
|
459
|
-
|
|
471
|
+
log.log(`[STEER-QUEUE] Signal not yet created, polling`);
|
|
460
472
|
for (let i = 0; i < 50; i++) {
|
|
461
473
|
await new Promise(r => setTimeout(r, 100));
|
|
462
474
|
signal = streamingSignals.get(sessionId);
|
|
463
475
|
if (signal)
|
|
464
476
|
break;
|
|
465
477
|
if (!hasActiveTask(sessionId)) {
|
|
466
|
-
|
|
478
|
+
log.log(`[STEER-QUEUE] First message completed while waiting, skip steer`);
|
|
467
479
|
return;
|
|
468
480
|
}
|
|
469
481
|
}
|
|
470
482
|
}
|
|
471
483
|
if (signal) {
|
|
472
|
-
|
|
484
|
+
log.log(`[STEER-QUEUE] Waiting for streaming signal`);
|
|
473
485
|
await signal.promise;
|
|
474
|
-
|
|
486
|
+
log.log(`[STEER-QUEUE] Streaming signal received`);
|
|
475
487
|
}
|
|
476
488
|
else {
|
|
477
489
|
// 轮询超时且 hasActiveTask 仍为 true——说明第一条消息可能卡在异常路径,
|
|
478
490
|
// 没有创建 signal。此时 dispatch 会与第一条消息的模型调用并发冲突,放弃。
|
|
479
|
-
|
|
491
|
+
log.log(`[STEER-QUEUE] Signal never appeared after polling, skip steer to avoid collision`);
|
|
480
492
|
return;
|
|
481
493
|
}
|
|
482
494
|
// 2. 第一条消息已结束 → 放弃
|
|
483
495
|
if (!hasActiveTask(sessionId)) {
|
|
484
|
-
|
|
496
|
+
log.log(`[STEER-QUEUE] First message completed, skip steer`);
|
|
485
497
|
return;
|
|
486
498
|
}
|
|
487
499
|
// 3. 构建 dispatch 上下文并 dispatch /steer
|
|
@@ -543,11 +555,11 @@ async function dispatchSteerWhenReady(params) {
|
|
|
543
555
|
agentId: params.route.accountId,
|
|
544
556
|
deviceType: params.deviceType,
|
|
545
557
|
};
|
|
546
|
-
|
|
558
|
+
log.log(`[STEER-QUEUE] Dispatching steer`);
|
|
547
559
|
await core.channel.reply.withReplyDispatcher({
|
|
548
560
|
dispatcher,
|
|
549
561
|
onSettled: () => {
|
|
550
|
-
|
|
562
|
+
log.log(`[STEER-QUEUE] Steer dispatch settled`);
|
|
551
563
|
},
|
|
552
564
|
run: () => {
|
|
553
565
|
return runWithSessionContext(sessionContext, async () => {
|
|
@@ -557,10 +569,10 @@ async function dispatchSteerWhenReady(params) {
|
|
|
557
569
|
dispatcher,
|
|
558
570
|
replyOptions,
|
|
559
571
|
});
|
|
560
|
-
|
|
572
|
+
log.log(`[STEER-QUEUE] dispatch result: ${JSON.stringify(result)}`);
|
|
561
573
|
return result;
|
|
562
574
|
});
|
|
563
575
|
},
|
|
564
576
|
});
|
|
565
|
-
|
|
577
|
+
log.log(`[STEER-QUEUE] Steer dispatch completed`);
|
|
566
578
|
}
|
|
@@ -46,23 +46,19 @@ 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
|
-
// 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`);
|
|
53
49
|
// 初始taskId和messageId(作为fallback)
|
|
54
50
|
const initialTaskId = taskId;
|
|
55
51
|
const initialMessageId = messageId;
|
|
56
|
-
/**
|
|
57
|
-
* 🔑 核心改造:动态获取当前活跃的taskId和messageId
|
|
58
|
-
* 每次需要taskId时,都从TaskManager获取最新值
|
|
59
|
-
*/
|
|
60
52
|
const getActiveTaskId = () => {
|
|
61
53
|
return getCurrentTaskId(sessionId) ?? initialTaskId;
|
|
62
54
|
};
|
|
63
55
|
const getActiveMessageId = () => {
|
|
64
56
|
return getCurrentMessageId(sessionId) ?? initialMessageId;
|
|
65
57
|
};
|
|
58
|
+
// Create a scoped logger that always uses this session's sessionId
|
|
59
|
+
// and dynamically resolves the latest taskId
|
|
60
|
+
const scopedLog = () => logger.withContext(sessionId, getActiveTaskId());
|
|
61
|
+
scopedLog().log(`[DISPATCHER-CREATE] Creating dispatcher`);
|
|
66
62
|
const core = getXYRuntime();
|
|
67
63
|
const config = resolveXYConfig(cfg);
|
|
68
64
|
// Simplified prefix context for single-account Xiaoyi channel
|