@ynhcj/xiaoyi-channel 0.0.28-beta → 0.0.30-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.
@@ -9,7 +9,19 @@ import { logger } from "../utils/logger.js";
9
9
  export const searchFileTool = {
10
10
  name: "search_file",
11
11
  label: "Search File",
12
- description: "搜索手机文件系统的文件。根据关键词搜索文件名称或内容,返回匹配的文件列表(包括文件名、路径、大小、修改时间等信息)。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。",
12
+ description: `搜索手机文件系统的文件。
13
+
14
+ 【重要】使用约束:此工具仅在用户显著说明要从手机搜索时才执行,例如:
15
+ - "从我手机里面搜索xxxx"
16
+ - "从手机文件系统找一下xxxx"
17
+ - "在手机上查找文件xxxx"
18
+ - "搜索手机里的文件"
19
+
20
+ 如果用户没有明确说明从手机搜索(如仅说"搜索文件"、"找一下xxxx"),应默认从 openclaw 本地的文件系统查询,不要调用此工具。
21
+
22
+ 功能说明:根据关键词搜索文件名称或内容,返回匹配的文件列表(包括文件名、路径、大小、修改时间等信息)。
23
+
24
+ 注意事项:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。`,
13
25
  parameters: {
14
26
  type: "object",
15
27
  properties: {
@@ -112,7 +112,7 @@ c. fileLocalUrls 与 fileRemoteUrls 任意一个不为空即可,两者都提
112
112
 
113
113
  注意事项:
114
114
  a. 支持传入数组或 JSON 字符串格式
115
- b. 操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次`,
115
+ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如果超时或失败,最多重试一次`,
116
116
  parameters: {
117
117
  type: "object",
118
118
  properties: {
@@ -125,166 +125,194 @@ b. 操作超时时间为60秒,请勿重复调用此工具,如果超时或失
125
125
  },
126
126
  },
127
127
  async execute(toolCallId, params) {
128
- logger.log(`[SEND_FILE_TO_USER_TOOL] 🚀 Starting execution`);
129
- logger.log(`[SEND_FILE_TO_USER_TOOL] - toolCallId: ${toolCallId}`);
130
- logger.log(`[SEND_FILE_TO_USER_TOOL] - params (raw):`, JSON.stringify(params));
131
- logger.log(`[SEND_FILE_TO_USER_TOOL] - timestamp: ${new Date().toISOString()}`);
132
- // Validate that at least one parameter is provided
133
- if (!params.fileLocalUrls && !params.fileRemoteUrls) {
134
- logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ Missing both fileLocalUrls and fileRemoteUrls parameters`);
135
- throw new Error("At least one of fileLocalUrls or fileRemoteUrls must be provided");
136
- }
137
- // Normalize fileLocalUrls parameter
138
- let fileLocalUrls = [];
139
- if (params.fileLocalUrls) {
140
- logger.log(`[SEND_FILE_TO_USER_TOOL] 🔄 Normalizing fileLocalUrls parameter...`);
141
- fileLocalUrls = normalizeToArray(params.fileLocalUrls);
142
- if (fileLocalUrls.length === 0) {
143
- logger.error(`[SEND_FILE_TO_USER_TOOL] fileLocalUrls array is empty`);
144
- throw new Error("fileLocalUrls array cannot be empty");
145
- }
146
- logger.log(`[SEND_FILE_TO_USER_TOOL] Normalized fileLocalUrls:`, JSON.stringify(fileLocalUrls));
147
- }
148
- // Normalize fileRemoteUrls parameter
149
- let fileRemoteUrls = [];
150
- if (params.fileRemoteUrls) {
151
- logger.log(`[SEND_FILE_TO_USER_TOOL] 🔄 Normalizing fileRemoteUrls parameter...`);
152
- fileRemoteUrls = normalizeToArray(params.fileRemoteUrls);
153
- if (fileRemoteUrls.length === 0) {
154
- logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ fileRemoteUrls array is empty`);
155
- throw new Error("fileRemoteUrls array cannot be empty");
128
+ // Set timeout for the entire operation (2 minutes)
129
+ const TOOL_TIMEOUT = 120000; // 2 minutes in milliseconds
130
+ let timeoutHandle = null;
131
+ // Create a timeout promise
132
+ const timeoutPromise = new Promise((_, reject) => {
133
+ timeoutHandle = setTimeout(() => {
134
+ reject(new Error("操作超时(2分钟)"));
135
+ }, TOOL_TIMEOUT);
136
+ });
137
+ // Create the main execution promise
138
+ const executionPromise = (async () => {
139
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 🚀 Starting execution`);
140
+ logger.log(`[SEND_FILE_TO_USER_TOOL] - toolCallId: ${toolCallId}`);
141
+ logger.log(`[SEND_FILE_TO_USER_TOOL] - params (raw):`, JSON.stringify(params));
142
+ logger.log(`[SEND_FILE_TO_USER_TOOL] - timestamp: ${new Date().toISOString()}`);
143
+ // Validate that at least one parameter is provided
144
+ if (!params.fileLocalUrls && !params.fileRemoteUrls) {
145
+ logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ Missing both fileLocalUrls and fileRemoteUrls parameters`);
146
+ throw new Error("At least one of fileLocalUrls or fileRemoteUrls must be provided");
156
147
  }
157
- logger.log(`[SEND_FILE_TO_USER_TOOL] Normalized fileRemoteUrls:`, JSON.stringify(fileRemoteUrls));
158
- }
159
- // Get session context
160
- logger.log(`[SEND_FILE_TO_USER_TOOL] 🔍 Attempting to get session context...`);
161
- const sessionContext = getCurrentSessionContext();
162
- if (!sessionContext) {
163
- logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ FAILED: No active session found!`);
164
- logger.error(`[SEND_FILE_TO_USER_TOOL] - toolCallId: ${toolCallId}`);
165
- throw new Error("No active XY session found. Send file to user tool can only be used during an active conversation.");
166
- }
167
- logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ Session context found`);
168
- logger.log(`[SEND_FILE_TO_USER_TOOL] - sessionId: ${sessionContext.sessionId}`);
169
- logger.log(`[SEND_FILE_TO_USER_TOOL] - taskId: ${sessionContext.taskId}`);
170
- logger.log(`[SEND_FILE_TO_USER_TOOL] - messageId: ${sessionContext.messageId}`);
171
- const { config, sessionId, taskId, messageId } = sessionContext;
172
- // Get WebSocket manager
173
- logger.log(`[SEND_FILE_TO_USER_TOOL] 🔌 Getting WebSocket manager...`);
174
- const wsManager = getXYWebSocketManager(config);
175
- logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ WebSocket manager obtained`);
176
- // Create upload service
177
- const uploadService = new XYFileUploadService(config.fileUploadUrl, config.apiKey, config.uid);
178
- // Collect all local file paths to upload
179
- const allLocalPaths = [...fileLocalUrls];
180
- const downloadedFiles = [];
181
- // Download remote files to local temp directory
182
- if (fileRemoteUrls.length > 0) {
183
- logger.log(`[SEND_FILE_TO_USER_TOOL] 📥 Downloading ${fileRemoteUrls.length} remote files...`);
184
- for (let i = 0; i < fileRemoteUrls.length; i++) {
185
- const remoteUrl = fileRemoteUrls[i];
186
- logger.log(`[SEND_FILE_TO_USER_TOOL] 📥 Downloading remote file ${i + 1}/${fileRemoteUrls.length}: ${remoteUrl}`);
187
- try {
188
- const localPath = await downloadRemoteFile(remoteUrl);
189
- allLocalPaths.push(localPath);
190
- downloadedFiles.push(localPath);
191
- }
192
- catch (error) {
193
- logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ Failed to download file ${i + 1}:`, error);
194
- throw new Error(`Failed to download remote file ${remoteUrl}: ${error instanceof Error ? error.message : String(error)}`);
148
+ // Normalize fileLocalUrls parameter
149
+ let fileLocalUrls = [];
150
+ if (params.fileLocalUrls) {
151
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 🔄 Normalizing fileLocalUrls parameter...`);
152
+ fileLocalUrls = normalizeToArray(params.fileLocalUrls);
153
+ if (fileLocalUrls.length === 0) {
154
+ logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ fileLocalUrls array is empty`);
155
+ throw new Error("fileLocalUrls array cannot be empty");
195
156
  }
157
+ logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ Normalized fileLocalUrls:`, JSON.stringify(fileLocalUrls));
196
158
  }
197
- logger.log(`[SEND_FILE_TO_USER_TOOL] Downloaded ${downloadedFiles.length} remote files`);
198
- }
199
- // Upload all local files and get fileIds
200
- logger.log(`[SEND_FILE_TO_USER_TOOL] 📤 Uploading ${allLocalPaths.length} files...`);
201
- const uploadedFiles = [];
202
- for (let i = 0; i < allLocalPaths.length; i++) {
203
- const localPath = allLocalPaths[i];
204
- logger.log(`[SEND_FILE_TO_USER_TOOL] 📤 Uploading file ${i + 1}/${allLocalPaths.length}: ${localPath}`);
205
- try {
206
- // Upload file using three-phase upload
207
- const fileId = await uploadService.uploadFile(localPath);
208
- if (!fileId) {
209
- logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ Failed to upload file: ${localPath} (fileId is empty)`);
210
- throw new Error(`Failed to upload file: ${localPath}`);
159
+ // Normalize fileRemoteUrls parameter
160
+ let fileRemoteUrls = [];
161
+ if (params.fileRemoteUrls) {
162
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 🔄 Normalizing fileRemoteUrls parameter...`);
163
+ fileRemoteUrls = normalizeToArray(params.fileRemoteUrls);
164
+ if (fileRemoteUrls.length === 0) {
165
+ logger.error(`[SEND_FILE_TO_USER_TOOL] fileRemoteUrls array is empty`);
166
+ throw new Error("fileRemoteUrls array cannot be empty");
211
167
  }
212
- // Get filename and mime type
213
- const fileName = localPath.split("/").pop() || "unknown";
214
- const mimeType = getMimeTypeFromFilename(fileName);
215
- uploadedFiles.push({ fileName, fileId, mimeType });
216
- logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ File uploaded successfully: ${fileName} -> ${fileId}`);
168
+ logger.log(`[SEND_FILE_TO_USER_TOOL] Normalized fileRemoteUrls:`, JSON.stringify(fileRemoteUrls));
217
169
  }
218
- catch (error) {
219
- logger.error(`[SEND_FILE_TO_USER_TOOL] Failed to upload file ${i + 1}:`, error);
220
- throw new Error(`Failed to upload file ${localPath}: ${error instanceof Error ? error.message : String(error)}`);
170
+ // Get session context
171
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 🔍 Attempting to get session context...`);
172
+ const sessionContext = getCurrentSessionContext();
173
+ if (!sessionContext) {
174
+ logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ FAILED: No active session found!`);
175
+ logger.error(`[SEND_FILE_TO_USER_TOOL] - toolCallId: ${toolCallId}`);
176
+ throw new Error("No active XY session found. Send file to user tool can only be used during an active conversation.");
221
177
  }
222
- }
223
- // Clean up downloaded files
224
- if (downloadedFiles.length > 0) {
225
- logger.log(`[SEND_FILE_TO_USER_TOOL] 🧹 Cleaning up ${downloadedFiles.length} downloaded files...`);
226
- for (const downloadedFile of downloadedFiles) {
178
+ logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ Session context found`);
179
+ logger.log(`[SEND_FILE_TO_USER_TOOL] - sessionId: ${sessionContext.sessionId}`);
180
+ logger.log(`[SEND_FILE_TO_USER_TOOL] - taskId: ${sessionContext.taskId}`);
181
+ logger.log(`[SEND_FILE_TO_USER_TOOL] - messageId: ${sessionContext.messageId}`);
182
+ const { config, sessionId, taskId, messageId } = sessionContext;
183
+ // Get WebSocket manager
184
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 🔌 Getting WebSocket manager...`);
185
+ const wsManager = getXYWebSocketManager(config);
186
+ logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ WebSocket manager obtained`);
187
+ // Create upload service
188
+ const uploadService = new XYFileUploadService(config.fileUploadUrl, config.apiKey, config.uid);
189
+ // Collect all local file paths to upload
190
+ const allLocalPaths = [...fileLocalUrls];
191
+ const downloadedFiles = [];
192
+ // Download remote files to local temp directory
193
+ if (fileRemoteUrls.length > 0) {
194
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 📥 Downloading ${fileRemoteUrls.length} remote files...`);
195
+ for (let i = 0; i < fileRemoteUrls.length; i++) {
196
+ const remoteUrl = fileRemoteUrls[i];
197
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 📥 Downloading remote file ${i + 1}/${fileRemoteUrls.length}: ${remoteUrl}`);
198
+ try {
199
+ const localPath = await downloadRemoteFile(remoteUrl);
200
+ allLocalPaths.push(localPath);
201
+ downloadedFiles.push(localPath);
202
+ }
203
+ catch (error) {
204
+ logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ Failed to download file ${i + 1}:`, error);
205
+ throw new Error(`Failed to download remote file ${remoteUrl}: ${error instanceof Error ? error.message : String(error)}`);
206
+ }
207
+ }
208
+ logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ Downloaded ${downloadedFiles.length} remote files`);
209
+ }
210
+ // Upload all local files and get fileIds
211
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 📤 Uploading ${allLocalPaths.length} files...`);
212
+ const uploadedFiles = [];
213
+ for (let i = 0; i < allLocalPaths.length; i++) {
214
+ const localPath = allLocalPaths[i];
215
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 📤 Uploading file ${i + 1}/${allLocalPaths.length}: ${localPath}`);
227
216
  try {
228
- await fs.unlink(downloadedFile);
229
- logger.log(`[SEND_FILE_TO_USER_TOOL] Cleaned up: ${downloadedFile}`);
217
+ // Upload file using three-phase upload
218
+ const fileId = await uploadService.uploadFile(localPath);
219
+ if (!fileId) {
220
+ logger.error(`[SEND_FILE_TO_USER_TOOL] ❌ Failed to upload file: ${localPath} (fileId is empty)`);
221
+ throw new Error(`Failed to upload file: ${localPath}`);
222
+ }
223
+ // Get filename and mime type
224
+ const fileName = localPath.split("/").pop() || "unknown";
225
+ const mimeType = getMimeTypeFromFilename(fileName);
226
+ uploadedFiles.push({ fileName, fileId, mimeType });
227
+ logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ File uploaded successfully: ${fileName} -> ${fileId}`);
230
228
  }
231
229
  catch (error) {
232
- logger.warn(`[SEND_FILE_TO_USER_TOOL] ⚠️ Failed to clean up file ${downloadedFile}:`, error);
230
+ logger.error(`[SEND_FILE_TO_USER_TOOL] Failed to upload file ${i + 1}:`, error);
231
+ throw new Error(`Failed to upload file ${localPath}: ${error instanceof Error ? error.message : String(error)}`);
233
232
  }
234
233
  }
235
- }
236
- // Build and send agent_response messages for each file
237
- logger.log(`[SEND_FILE_TO_USER_TOOL] 📦 Building and sending agent_response messages...`);
238
- const sentFiles = [];
239
- for (const uploadedFile of uploadedFiles) {
240
- const { fileName, fileId, mimeType } = uploadedFile;
241
- const agentResponse = {
242
- msgType: "agent_response",
243
- agentId: config.agentId,
244
- sessionId: sessionId,
245
- taskId: taskId,
246
- msgDetail: JSON.stringify({
247
- jsonrpc: "2.0",
248
- id: taskId,
249
- result: {
250
- kind: "artifact-update",
251
- append: true,
252
- lastChunk: false,
253
- final: false,
254
- artifact: {
255
- artifactId: taskId,
256
- parts: [
257
- {
258
- kind: "file",
259
- file: {
260
- name: fileName,
261
- mimeType: mimeType,
262
- fileId: fileId,
234
+ // Clean up downloaded files
235
+ if (downloadedFiles.length > 0) {
236
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 🧹 Cleaning up ${downloadedFiles.length} downloaded files...`);
237
+ for (const downloadedFile of downloadedFiles) {
238
+ try {
239
+ await fs.unlink(downloadedFile);
240
+ logger.log(`[SEND_FILE_TO_USER_TOOL] Cleaned up: ${downloadedFile}`);
241
+ }
242
+ catch (error) {
243
+ logger.warn(`[SEND_FILE_TO_USER_TOOL] ⚠️ Failed to clean up file ${downloadedFile}:`, error);
244
+ }
245
+ }
246
+ }
247
+ // Build and send agent_response messages for each file
248
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 📦 Building and sending agent_response messages...`);
249
+ const sentFiles = [];
250
+ for (const uploadedFile of uploadedFiles) {
251
+ const { fileName, fileId, mimeType } = uploadedFile;
252
+ const agentResponse = {
253
+ msgType: "agent_response",
254
+ agentId: config.agentId,
255
+ sessionId: sessionId,
256
+ taskId: taskId,
257
+ msgDetail: JSON.stringify({
258
+ jsonrpc: "2.0",
259
+ id: taskId,
260
+ result: {
261
+ kind: "artifact-update",
262
+ append: true,
263
+ lastChunk: false,
264
+ final: false,
265
+ artifact: {
266
+ artifactId: taskId,
267
+ parts: [
268
+ {
269
+ kind: "file",
270
+ file: {
271
+ name: fileName,
272
+ mimeType: mimeType,
273
+ fileId: fileId,
274
+ },
263
275
  },
264
- },
265
- ],
276
+ ],
277
+ },
266
278
  },
279
+ error: { code: 0 },
280
+ }),
281
+ };
282
+ // Send WebSocket message
283
+ await wsManager.sendMessage(sessionId, agentResponse);
284
+ sentFiles.push({ fileName, fileId });
285
+ logger.log(`[SEND_FILE_TO_USER_TOOL] ✅ Sent file to user: ${fileName} (fileId: ${fileId})`);
286
+ }
287
+ logger.log(`[SEND_FILE_TO_USER_TOOL] 🎉 Successfully sent ${sentFiles.length} files to user`);
288
+ return {
289
+ content: [
290
+ {
291
+ type: "text",
292
+ text: JSON.stringify({
293
+ sentFiles,
294
+ count: sentFiles.length,
295
+ message: `成功发送 ${sentFiles.length} 个文件到用户设备`
296
+ }),
267
297
  },
268
- error: { code: 0 },
269
- }),
298
+ ],
270
299
  };
271
- // Send WebSocket message
272
- await wsManager.sendMessage(sessionId, agentResponse);
273
- sentFiles.push({ fileName, fileId });
274
- logger.log(`[SEND_FILE_TO_USER_TOOL] Sent file to user: ${fileName} (fileId: ${fileId})`);
300
+ })();
301
+ // Race between execution and timeout
302
+ try {
303
+ const result = await Promise.race([executionPromise, timeoutPromise]);
304
+ // Clear timeout if execution completed
305
+ if (timeoutHandle) {
306
+ clearTimeout(timeoutHandle);
307
+ }
308
+ return result;
309
+ }
310
+ catch (error) {
311
+ // Clear timeout on error
312
+ if (timeoutHandle) {
313
+ clearTimeout(timeoutHandle);
314
+ }
315
+ throw error;
275
316
  }
276
- logger.log(`[SEND_FILE_TO_USER_TOOL] 🎉 Successfully sent ${sentFiles.length} files to user`);
277
- return {
278
- content: [
279
- {
280
- type: "text",
281
- text: JSON.stringify({
282
- sentFiles,
283
- count: sentFiles.length,
284
- message: `成功发送 ${sentFiles.length} 个文件到用户设备`
285
- }),
286
- },
287
- ],
288
- };
289
317
  },
290
318
  };
@@ -37,5 +37,8 @@ export declare function runWithSessionContext<T>(context: SessionContext, callba
37
37
  * Get the current session context from AsyncLocalStorage.
38
38
  * This is the recommended way to access session context in tools.
39
39
  * Returns null if not running within a session context.
40
+ *
41
+ * Enhanced version: Automatically fetches the latest taskId from task-manager
42
+ * to support interruption scenarios where a new message updates the taskId.
40
43
  */
41
44
  export declare function getCurrentSessionContext(): SessionContext | null;
@@ -3,6 +3,7 @@
3
3
  import { AsyncLocalStorage } from "async_hooks";
4
4
  import { logger } from "../utils/logger.js";
5
5
  import { configManager } from "../utils/config-manager.js";
6
+ import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
6
7
  // Map of sessionKey -> SessionContextWithRef
7
8
  const activeSessions = new Map();
8
9
  // AsyncLocalStorage for thread-safe session context isolation
@@ -116,16 +117,39 @@ export function runWithSessionContext(context, callback) {
116
117
  * Get the current session context from AsyncLocalStorage.
117
118
  * This is the recommended way to access session context in tools.
118
119
  * Returns null if not running within a session context.
120
+ *
121
+ * Enhanced version: Automatically fetches the latest taskId from task-manager
122
+ * to support interruption scenarios where a new message updates the taskId.
119
123
  */
120
124
  export function getCurrentSessionContext() {
125
+ // 1. Get base context from AsyncLocalStorage
121
126
  const context = asyncLocalStorage.getStore() ?? null;
122
- if (context) {
123
- logger.log(`[SESSION_MANAGER] ✅ Got current session context from AsyncLocalStorage`);
124
- logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
125
- logger.log(`[SESSION_MANAGER] - taskId: ${context.taskId}`);
126
- }
127
- else {
127
+ if (!context) {
128
128
  logger.warn(`[SESSION_MANAGER] ⚠️ No session context in AsyncLocalStorage`);
129
+ return null;
130
+ }
131
+ // 2. Get latest taskId and messageId from task-manager
132
+ const latestTaskId = getCurrentTaskId(context.sessionId);
133
+ const latestMessageId = getCurrentMessageId(context.sessionId);
134
+ // 3. If task-manager has a newer taskId, use the latest value
135
+ if (latestTaskId && latestTaskId !== context.taskId) {
136
+ logger.log(`[SESSION_MANAGER] 🔄 TaskId updated (interruption detected)`);
137
+ logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
138
+ logger.log(`[SESSION_MANAGER] - Old taskId: ${context.taskId}`);
139
+ logger.log(`[SESSION_MANAGER] - New taskId: ${latestTaskId}`);
140
+ logger.log(`[SESSION_MANAGER] - Old messageId: ${context.messageId}`);
141
+ logger.log(`[SESSION_MANAGER] - New messageId: ${latestMessageId ?? context.messageId}`);
142
+ // Return updated context (create new object, don't modify original)
143
+ return {
144
+ ...context,
145
+ taskId: latestTaskId,
146
+ messageId: latestMessageId ?? context.messageId,
147
+ };
129
148
  }
149
+ // 4. No update needed, return original context
150
+ logger.log(`[SESSION_MANAGER] ✅ Got current session context from AsyncLocalStorage`);
151
+ logger.log(`[SESSION_MANAGER] - sessionId: ${context.sessionId}`);
152
+ logger.log(`[SESSION_MANAGER] - taskId: ${context.taskId}`);
153
+ logger.log(`[SESSION_MANAGER] - messageId: ${context.messageId}`);
130
154
  return context;
131
155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.28-beta",
3
+ "version": "0.0.30-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",