@ynhcj/xiaoyi-channel 0.0.103-next → 0.0.104-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.
Files changed (99) hide show
  1. package/dist/src/bot.js +27 -60
  2. package/dist/src/channel.js +13 -5
  3. package/dist/src/formatter.js +5 -5
  4. package/dist/src/monitor.js +12 -12
  5. package/dist/src/outbound.js +5 -5
  6. package/dist/src/provider.js +23 -28
  7. package/dist/src/reply-dispatcher.js +5 -3
  8. package/dist/src/steer-injector.js +5 -7
  9. package/dist/src/tools/calendar-tool.d.ts +1 -2
  10. package/dist/src/tools/calendar-tool.js +8 -7
  11. package/dist/src/tools/call-device-tool.d.ts +1 -2
  12. package/dist/src/tools/call-device-tool.js +31 -31
  13. package/dist/src/tools/call-phone-tool.d.ts +1 -2
  14. package/dist/src/tools/call-phone-tool.js +8 -7
  15. package/dist/src/tools/create-alarm-tool.d.ts +1 -2
  16. package/dist/src/tools/create-alarm-tool.js +8 -7
  17. package/dist/src/tools/create-all-tools.d.ts +7 -8
  18. package/dist/src/tools/create-all-tools.js +20 -24
  19. package/dist/src/tools/delete-alarm-tool.d.ts +1 -2
  20. package/dist/src/tools/delete-alarm-tool.js +8 -7
  21. package/dist/src/tools/get-alarm-tool-schema.d.ts +1 -2
  22. package/dist/src/tools/get-alarm-tool-schema.js +5 -5
  23. package/dist/src/tools/get-calendar-tool-schema.d.ts +1 -2
  24. package/dist/src/tools/get-calendar-tool-schema.js +3 -3
  25. package/dist/src/tools/get-collection-tool-schema.d.ts +1 -2
  26. package/dist/src/tools/get-collection-tool-schema.js +2 -2
  27. package/dist/src/tools/get-contact-tool-schema.d.ts +1 -2
  28. package/dist/src/tools/get-contact-tool-schema.js +5 -5
  29. package/dist/src/tools/get-device-file-tool-schema.d.ts +1 -2
  30. package/dist/src/tools/get-device-file-tool-schema.js +4 -4
  31. package/dist/src/tools/get-email-tool-schema.d.ts +1 -2
  32. package/dist/src/tools/get-email-tool-schema.js +3 -3
  33. package/dist/src/tools/get-note-tool-schema.d.ts +1 -2
  34. package/dist/src/tools/get-note-tool-schema.js +4 -4
  35. package/dist/src/tools/get-photo-tool-schema.d.ts +1 -2
  36. package/dist/src/tools/get-photo-tool-schema.js +3 -3
  37. package/dist/src/tools/image-reading-tool.d.ts +1 -2
  38. package/dist/src/tools/image-reading-tool.js +5 -4
  39. package/dist/src/tools/location-tool.d.ts +1 -2
  40. package/dist/src/tools/location-tool.js +8 -7
  41. package/dist/src/tools/login-token-tool.d.ts +1 -2
  42. package/dist/src/tools/login-token-tool.js +9 -9
  43. package/dist/src/tools/modify-alarm-tool.d.ts +1 -2
  44. package/dist/src/tools/modify-alarm-tool.js +8 -7
  45. package/dist/src/tools/modify-note-tool.d.ts +1 -2
  46. package/dist/src/tools/modify-note-tool.js +8 -7
  47. package/dist/src/tools/note-tool.d.ts +1 -2
  48. package/dist/src/tools/note-tool.js +8 -7
  49. package/dist/src/tools/query-app-message-tool.d.ts +1 -2
  50. package/dist/src/tools/query-app-message-tool.js +8 -7
  51. package/dist/src/tools/query-memory-data-tool.d.ts +1 -2
  52. package/dist/src/tools/query-memory-data-tool.js +8 -7
  53. package/dist/src/tools/query-todo-task-tool.d.ts +1 -2
  54. package/dist/src/tools/query-todo-task-tool.js +8 -7
  55. package/dist/src/tools/save-file-to-phone-tool.d.ts +1 -2
  56. package/dist/src/tools/save-file-to-phone-tool.js +9 -8
  57. package/dist/src/tools/save-media-to-gallery-tool.d.ts +1 -2
  58. package/dist/src/tools/save-media-to-gallery-tool.js +9 -8
  59. package/dist/src/tools/save-self-evolution-skill-tool.d.ts +1 -2
  60. package/dist/src/tools/save-self-evolution-skill-tool.js +4 -3
  61. package/dist/src/tools/search-alarm-tool.d.ts +1 -2
  62. package/dist/src/tools/search-alarm-tool.js +8 -7
  63. package/dist/src/tools/search-calendar-tool.d.ts +1 -2
  64. package/dist/src/tools/search-calendar-tool.js +8 -7
  65. package/dist/src/tools/search-contact-tool.d.ts +1 -2
  66. package/dist/src/tools/search-contact-tool.js +8 -7
  67. package/dist/src/tools/search-email-tool.d.ts +1 -2
  68. package/dist/src/tools/search-email-tool.js +8 -7
  69. package/dist/src/tools/search-file-tool.d.ts +1 -2
  70. package/dist/src/tools/search-file-tool.js +8 -7
  71. package/dist/src/tools/search-message-tool.d.ts +1 -2
  72. package/dist/src/tools/search-message-tool.js +8 -7
  73. package/dist/src/tools/search-note-tool.d.ts +1 -2
  74. package/dist/src/tools/search-note-tool.js +8 -7
  75. package/dist/src/tools/search-photo-gallery-tool.d.ts +1 -2
  76. package/dist/src/tools/search-photo-gallery-tool.js +5 -4
  77. package/dist/src/tools/send-email-tool.d.ts +1 -2
  78. package/dist/src/tools/send-email-tool.js +8 -7
  79. package/dist/src/tools/send-file-to-user-tool.d.ts +1 -2
  80. package/dist/src/tools/send-file-to-user-tool.js +11 -12
  81. package/dist/src/tools/send-message-tool.d.ts +1 -2
  82. package/dist/src/tools/send-message-tool.js +8 -7
  83. package/dist/src/tools/session-helper.d.ts +13 -0
  84. package/dist/src/tools/session-helper.js +19 -0
  85. package/dist/src/tools/upload-file-tool.d.ts +1 -2
  86. package/dist/src/tools/upload-file-tool.js +5 -4
  87. package/dist/src/tools/upload-photo-tool.d.ts +1 -2
  88. package/dist/src/tools/upload-photo-tool.js +5 -4
  89. package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +1 -2
  90. package/dist/src/tools/xiaoyi-add-collection-tool.js +9 -8
  91. package/dist/src/tools/xiaoyi-collection-tool.d.ts +1 -2
  92. package/dist/src/tools/xiaoyi-collection-tool.js +8 -7
  93. package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +1 -2
  94. package/dist/src/tools/xiaoyi-delete-collection-tool.js +8 -7
  95. package/dist/src/tools/xiaoyi-gui-tool.d.ts +1 -2
  96. package/dist/src/tools/xiaoyi-gui-tool.js +10 -10
  97. package/dist/src/xy-session-store.d.ts +79 -0
  98. package/dist/src/xy-session-store.js +153 -0
  99. package/package.json +1 -1
package/dist/src/bot.js CHANGED
@@ -6,14 +6,13 @@ import { downloadFilesFromParts } from "./file-download.js";
6
6
  import { resolveXYConfig } from "./config.js";
7
7
  import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, sendA2AResponse } from "./formatter.js";
8
8
  import { appendSelfEvolutionKeywordNudge, shouldNudgeForSelfEvolutionKeyword, } from "./self-evolution-keyword.js";
9
- import { registerSession, unregisterSession, runWithSessionContext } from "./tools/session-manager.js";
10
9
  import { configManager } from "./utils/config-manager.js";
11
10
  import { addPushId } from "./utils/pushid-manager.js";
12
11
  import { getPushDataById } from "./utils/pushdata-manager.js";
13
12
  import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
14
13
  import { saveRuntimeInfo } from "./utils/runtime-manager.js";
15
14
  import { toolCallNudgeManager } from "./utils/tool-call-nudge-manager.js";
16
- import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActiveTask, } from "./task-manager.js";
15
+ import { registerSession, unregisterSession, hasActiveSession, xyAsyncLocalStorage, } from "./xy-session-store.js";
17
16
  /**
18
17
  * Handle an incoming A2A message.
19
18
  * This is the main entry point for message processing.
@@ -101,22 +100,26 @@ export async function handleXYMessage(params) {
101
100
  }
102
101
  }
103
102
  // ========================================
104
- // 🔑 检测steer模式和是否是第二条消息
103
+ // 🔑 检测steer模式
104
+ // Resolve route early to determine steer state
105
+ const config = resolveXYConfig(cfg);
106
+ let route = core.channel.routing.resolveAgentRoute({
107
+ cfg,
108
+ channel: "xiaoyi-channel",
109
+ accountId,
110
+ peer: {
111
+ kind: "direct",
112
+ id: parsed.sessionId,
113
+ },
114
+ });
115
+ log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
105
116
  const isSteerMode = cfg.messages?.queue?.mode === "steer";
106
- const isSecondMessage = isSteerMode && hasActiveTask(parsed.sessionId);
117
+ const isSecondMessage = isSteerMode && hasActiveSession(route.sessionKey);
107
118
  if (isSecondMessage) {
108
119
  log(`[BOT] 🔄 STEER MODE - Second message detected (will be follower)`);
109
120
  log(`[BOT] - Session: ${parsed.sessionId}`);
110
121
  log(`[BOT] - New taskId: ${parsed.taskId} (will replace current)`);
111
122
  }
112
- // 🔑 注册taskId(第二条消息会覆盖第一条的taskId)
113
- const { isUpdate, refCount } = registerTaskId(parsed.sessionId, parsed.taskId, parsed.messageId, { incrementRef: true } // 增加引用计数
114
- );
115
- // 🔑 如果是第一条消息,锁定taskId防止被过早清理
116
- if (!isUpdate) {
117
- lockTaskId(parsed.sessionId);
118
- log(`[BOT] 🔒 Locked taskId for first message`);
119
- }
120
123
  // Extract and update push_id if present
121
124
  const pushId = extractPushId(parsed.parts);
122
125
  if (pushId) {
@@ -142,27 +145,13 @@ export async function handleXYMessage(params) {
142
145
  ).catch((err) => {
143
146
  error(`[BOT] Failed to save runtime info:`, err);
144
147
  });
145
- // Resolve configuration (needed for status updates)
146
- const config = resolveXYConfig(cfg);
147
- // ✅ Resolve agent route (following feishu pattern)
148
- // accountId is "default" for XY (single account mode)
149
- // Use sessionId as peer.id to ensure all messages in the same session share context
150
- let route = core.channel.routing.resolveAgentRoute({
151
- cfg,
152
- channel: "xiaoyi-channel",
153
- accountId, // "default"
154
- peer: {
155
- kind: "direct",
156
- id: parsed.sessionId, // ✅ Use sessionId to share context within the same conversation session
157
- },
158
- });
159
- log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
148
+ // 🔑 Register / update session in unified store
160
149
  registerSession(route.sessionKey, {
161
150
  config,
162
- sessionId: parsed.sessionId,
151
+ a2aSessionId: parsed.sessionId,
163
152
  taskId: parsed.taskId,
164
153
  messageId: parsed.messageId,
165
- agentId: route.accountId,
154
+ accountId: route.accountId,
166
155
  deviceType,
167
156
  });
168
157
  // 🔑 发送初始状态更新(第二条消息也要发,用新taskId)
@@ -263,35 +252,18 @@ export async function handleXYMessage(params) {
263
252
  startStatusInterval();
264
253
  log(`[BOT-DISPATCHER] ✅ Status interval started for first message`);
265
254
  }
266
- // Build session context for AsyncLocalStorage
267
- const sessionContext = {
268
- config,
269
- sessionId: parsed.sessionId,
270
- taskId: parsed.taskId,
271
- messageId: parsed.messageId,
272
- agentId: route.accountId,
273
- deviceType,
274
- };
275
255
  log(`[BOT-DISPATCH] ⏳ withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
276
256
  await core.channel.reply.withReplyDispatcher({
277
257
  dispatcher,
278
258
  onSettled: () => {
279
259
  log(`[BOT] 🏁 onSettled called for session: ${route.sessionKey}`);
280
- log(`[BOT] - isSecondMessage: ${isSecondMessage}`);
281
- // 🔑 减少引用计数
282
- decrementTaskIdRef(parsed.sessionId);
283
- // 🔑 如果是第一条消息完成,解锁
284
- if (!isSecondMessage) {
285
- unlockTaskId(parsed.sessionId);
286
- log(`[BOT] 🔓 Unlocked taskId (first message completed)`);
287
- }
288
- // 减少session引用计数
260
+ // Cleanup session from store
289
261
  unregisterSession(route.sessionKey);
290
262
  log(`[BOT] ✅ Cleanup completed`);
291
263
  },
292
264
  run: () =>
293
- // 🔐 Use AsyncLocalStorage to provide session context to tools
294
- runWithSessionContext(sessionContext, async () => {
265
+ // 🔐 Run inside ALS with openclawSessionKey so agentTools factory can read it
266
+ xyAsyncLocalStorage.run({ openclawSessionKey: route.sessionKey }, async () => {
295
267
  log(`[BOT-DISPATCH] ⏳ dispatchReplyFromConfig starting...`);
296
268
  log(`[BOT-DISPATCH] - sessionKey: ${ctxPayload.SessionKey}`);
297
269
  log(`[BOT-DISPATCH] - provider: ${ctxPayload.Provider}`);
@@ -326,18 +298,14 @@ export async function handleXYMessage(params) {
326
298
  error("Failed to handle XY message:", err);
327
299
  runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
328
300
  log(`[BOT] ❌ Error occurred, attempting cleanup...`);
329
- // 🔑 错误时也要清理taskId和session
301
+ // 🔑 错误时也要清理session
330
302
  try {
331
- const params = message.params;
332
- const sessionId = params?.sessionId;
303
+ const msgParams = message.params;
304
+ const sessionId = msgParams?.sessionId;
333
305
  if (sessionId) {
334
306
  log(`[BOT] 🧹 Cleaning up after error: ${sessionId}`);
335
- // 清理 taskId
336
- decrementTaskIdRef(sessionId);
337
- unlockTaskId(sessionId);
338
- // 清理 session
339
- const core = getXYRuntime();
340
- const route = core.channel.routing.resolveAgentRoute({
307
+ const core2 = getXYRuntime();
308
+ const errorRoute = core2.channel.routing.resolveAgentRoute({
341
309
  cfg,
342
310
  channel: "xiaoyi-channel",
343
311
  accountId,
@@ -346,13 +314,12 @@ export async function handleXYMessage(params) {
346
314
  id: sessionId,
347
315
  },
348
316
  });
349
- unregisterSession(route.sessionKey);
317
+ unregisterSession(errorRoute.sessionKey);
350
318
  log(`[BOT] ✅ Cleanup completed after error`);
351
319
  }
352
320
  }
353
321
  catch (cleanupErr) {
354
322
  log(`[BOT] ⚠️ Cleanup failed:`, cleanupErr);
355
- // Ignore cleanup errors
356
323
  }
357
324
  // ❌ Don't re-throw: message processing error should not affect gateway stability
358
325
  }
@@ -2,7 +2,7 @@ import { resolveXYConfig, listXYAccountIds, getDefaultXYAccountId } from "./conf
2
2
  import { xyConfigSchema } from "./config-schema.js";
3
3
  import { xyOutbound } from "./outbound.js";
4
4
  import { filterToolsByDevice } from "./tools/device-tool-map.js";
5
- import { getCurrentSessionContext } from "./tools/session-manager.js";
5
+ import { getSession, xyAsyncLocalStorage } from "./xy-session-store.js";
6
6
  import { createAllTools } from "./tools/create-all-tools.js";
7
7
  import { logger } from "./utils/logger.js";
8
8
  /**
@@ -44,10 +44,18 @@ export const xyPlugin = {
44
44
  },
45
45
  outbound: xyOutbound,
46
46
  agentTools: () => {
47
- const ctx = getCurrentSessionContext();
48
- const allTools = createAllTools(ctx);
49
- const filtered = filterToolsByDevice(allTools, ctx?.deviceType);
50
- logger.log(`[DEVICE-FILTER] deviceType=${ctx?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
47
+ // agentTools factory is called during agent run setup.
48
+ // ALS is still valid at this point — read sessionKey from it.
49
+ const alsContext = xyAsyncLocalStorage.getStore();
50
+ const sessionKey = alsContext?.openclawSessionKey;
51
+ if (!sessionKey) {
52
+ logger.log("[CHANNEL-TOOLS] no sessionKey in ALS, returning empty tools");
53
+ return [];
54
+ }
55
+ const session = getSession(sessionKey);
56
+ const allTools = createAllTools(sessionKey);
57
+ const filtered = filterToolsByDevice(allTools, session?.deviceType);
58
+ logger.log(`[CHANNEL-TOOLS] sessionKey=${sessionKey} deviceType=${session?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length} (${filtered.map(t => t.name).join(", ")})`);
51
59
  return filtered;
52
60
  },
53
61
  messaging: {
@@ -2,7 +2,7 @@
2
2
  import { v4 as uuidv4 } from "uuid";
3
3
  import { getXYWebSocketManager } from "./client.js";
4
4
  import { logger } from "./utils/logger.js";
5
- import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
5
+ import { getSessionByA2AId } from "./xy-session-store.js";
6
6
  /**
7
7
  * Send an A2A artifact update response.
8
8
  */
@@ -114,8 +114,8 @@ export async function sendStatusUpdate(params) {
114
114
  const log = runtime?.log ?? console.log;
115
115
  // Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
116
116
  // fall back to closure-captured values
117
- const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
118
- const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
117
+ const currentTaskId = getSessionByA2AId(sessionId)?.taskId ?? taskId;
118
+ const currentMessageId = getSessionByA2AId(sessionId)?.messageId ?? messageId;
119
119
  // Build status update event following A2A protocol standard
120
120
  const statusUpdate = {
121
121
  taskId: currentTaskId,
@@ -162,8 +162,8 @@ export async function sendCommand(params) {
162
162
  const { config, sessionId, taskId, messageId, command } = params;
163
163
  // Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
164
164
  // fall back to closure-captured values
165
- const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
166
- const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
165
+ const currentTaskId = getSessionByA2AId(sessionId)?.taskId ?? taskId;
166
+ const currentMessageId = getSessionByA2AId(sessionId)?.messageId ?? messageId;
167
167
  // Build artifact update with command as data
168
168
  // Wrap command in commands array as per protocol requirement
169
169
  const artifact = {
@@ -2,13 +2,13 @@ import { resolveXYConfig } from "./config.js";
2
2
  import { getXYWebSocketManager, setClientRuntime, diagnoseAllManagers, cleanupOrphanConnections, removeXYWebSocketManager } from "./client.js";
3
3
  import { handleXYMessage } from "./bot.js";
4
4
  import { parseA2AMessage } from "./parser.js";
5
- import { hasActiveTask, getAllActiveTaskBindings } from "./task-manager.js";
5
+ import { getAllActiveSessions, getSessionByA2AId } from "./xy-session-store.js";
6
6
  import { sendA2AResponse } from "./formatter.js";
7
7
  import { handleTriggerEvent } from "./trigger-handler.js";
8
8
  import { handleSelfEvolutionEvent, handleSelfEvolutionStateGetEvent } from "./self-evolution-handler.js";
9
9
  import { handleLoginTokenEvent } from "./login-token-handler.js";
10
10
  import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
11
- import { cleanupStaleSessions, getActiveSessionCount, cleanupAllSessions } from "./tools/session-manager.js";
11
+ import { cleanupStaleSessions, getActiveSessionCount, cleanupAllSessions } from "./xy-session-store.js";
12
12
  /**
13
13
  * Per-session serial queue that ensures messages from the same session are processed
14
14
  * in arrival order while allowing different sessions to run concurrently.
@@ -106,7 +106,7 @@ export async function monitorXYProvider(opts = {}) {
106
106
  try {
107
107
  const parsed = parseA2AMessage(message);
108
108
  const steerMode = cfg.messages?.queue?.mode === "steer";
109
- const hasActiveRun = hasActiveTask(parsed.sessionId);
109
+ const hasActiveRun = Boolean(getSessionByA2AId(parsed.sessionId));
110
110
  if (steerMode && hasActiveRun) {
111
111
  // Steer模式且有活跃任务:不入队列,直接并发执行
112
112
  log(`[MONITOR-HANDLER] 🔄 STEER MODE: Executing concurrently for messageKey=${messageKey}`);
@@ -214,25 +214,25 @@ export async function monitorXYProvider(opts = {}) {
214
214
  log("XY gateway: abort signal received, sending notifications before stopping");
215
215
  // 📤 Send restart notification to all active sessions before disconnecting
216
216
  try {
217
- const activeBindings = getAllActiveTaskBindings();
218
- if (activeBindings.length > 0) {
217
+ const activeSessions = getAllActiveSessions();
218
+ if (activeSessions.length > 0) {
219
219
  const config = resolveXYConfig(cfg);
220
220
  const notificationText = "Gateway即将重启,重启期间可能短暂出现\u201c环境异常\u201d提示,请稍候并耐心重试~";
221
- log(`[MONITOR] 📤 Sending restart notifications to ${activeBindings.length} active session(s)`);
222
- const sendPromises = activeBindings.map(binding => sendA2AResponse({
221
+ log(`[MONITOR] 📤 Sending restart notifications to ${activeSessions.length} active session(s)`);
222
+ const sendPromises = activeSessions.map(({ session }) => sendA2AResponse({
223
223
  config,
224
- sessionId: binding.sessionId,
225
- taskId: binding.currentTaskId,
226
- messageId: binding.currentMessageId,
224
+ sessionId: session.a2aSessionId,
225
+ taskId: session.taskId,
226
+ messageId: session.messageId,
227
227
  text: notificationText,
228
228
  append: false,
229
229
  final: true,
230
230
  runtime,
231
231
  }).catch(err => {
232
- error(`[MONITOR] Failed to send restart notification to session ${binding.sessionId}: ${String(err)}`);
232
+ error(`[MONITOR] Failed to send restart notification to session ${session.a2aSessionId}: ${String(err)}`);
233
233
  }));
234
234
  await Promise.all(sendPromises);
235
- log(`[MONITOR] ✅ Restart notifications sent to ${activeBindings.length} session(s)`);
235
+ log(`[MONITOR] ✅ Restart notifications sent to ${activeSessions.length} session(s)`);
236
236
  }
237
237
  else {
238
238
  log(`[MONITOR] No active sessions, skipping restart notifications`);
@@ -1,7 +1,7 @@
1
1
  import { resolveXYConfig } from "./config.js";
2
2
  import { XYFileUploadService } from "./file-upload.js";
3
3
  import { XYPushService } from "./push.js";
4
- import { getCurrentSessionContext } from "./tools/session-manager.js";
4
+ import { getSessionByA2AId } from "./xy-session-store.js";
5
5
  import { savePushData } from "./utils/pushdata-manager.js";
6
6
  import { getAllPushIds } from "./utils/pushid-manager.js";
7
7
  import { logger } from "./utils/logger.js";
@@ -68,10 +68,10 @@ export const xyOutbound = {
68
68
  // If the target doesn't contain "::", try to enhance it with taskId from session context
69
69
  if (!trimmedTo.includes("::")) {
70
70
  logger.log(`[xyOutbound.resolveTarget] Target "${trimmedTo}" missing taskId, looking up session context`);
71
- // Try to get the current session context
72
- const sessionContext = getCurrentSessionContext();
73
- if (sessionContext && sessionContext.sessionId === trimmedTo) {
74
- const enhancedTarget = `${trimmedTo}::${sessionContext.taskId}`;
71
+ // Try to get the current session context via A2A sessionId
72
+ const session = getSessionByA2AId(trimmedTo);
73
+ if (session) {
74
+ const enhancedTarget = `${trimmedTo}::${session.taskId}`;
75
75
  logger.log(`[xyOutbound.resolveTarget] Enhanced target: ${enhancedTarget}`);
76
76
  return {
77
77
  ok: true,
@@ -8,8 +8,7 @@
8
8
  // models.providers.xiaoyiprovider.api = "openai-completions"
9
9
  // models.providers.xiaoyiprovider.models = [...]
10
10
  import { createHash } from "crypto";
11
- import { getCurrentSessionContext } from "./tools/session-manager.js";
12
- import { getCurrentTaskId } from "./task-manager.js";
11
+ import { getSession, getSessionByA2AId, xyAsyncLocalStorage } from "./xy-session-store.js";
13
12
  import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
14
13
  // ── Retry config ──────────────────────────────────────────────
15
14
  const RETRY_DELAYS_MS = [10_000, 20_000, 40_000, 60_000, 60_000];
@@ -405,9 +404,12 @@ export const xiaoyiProvider = {
405
404
  * 3. No uid available → return undefined (no headers injected)
406
405
  */
407
406
  prepareExtraParams: (ctx) => {
408
- const sessionCtx = getCurrentSessionContext();
409
- if (sessionCtx) {
410
- const taskId = sessionCtx.taskId;
407
+ // Try to get session from ALS (valid during agent run setup)
408
+ const alsContext = xyAsyncLocalStorage.getStore();
409
+ const sessionKey = alsContext?.openclawSessionKey;
410
+ const session = sessionKey ? getSession(sessionKey) : null;
411
+ if (session) {
412
+ const taskId = session.taskId;
411
413
  const sessionId = taskId.split("&")[0];
412
414
  const interactionId = taskId.split("&")[1] || "";
413
415
  return {
@@ -415,7 +417,7 @@ export const xiaoyiProvider = {
415
417
  [HEADER_TRACE_ID]: taskId,
416
418
  [HEADER_SESSION_ID]: sessionId,
417
419
  [HEADER_INTERACTION_ID]: interactionId,
418
- [DEVICE_TYPE_KEY]: sessionCtx.deviceType ?? "",
420
+ [DEVICE_TYPE_KEY]: session.deviceType ?? "",
419
421
  };
420
422
  }
421
423
  // Fallback: store uid prefix for lazy timestamp generation in wrapStreamFn.
@@ -444,13 +446,13 @@ export const xiaoyiProvider = {
444
446
  if (!underlying)
445
447
  return underlying;
446
448
  // Capture A2A sessionId at agent setup time for multi-session isolation.
447
- // openclaw calls wrapStreamFn per-agent (per session), so this runs inside
448
- // the correct runWithSessionContext() ALS scope. When multiple sessions are
449
- // active concurrently, getCurrentSessionContext() may later return the WRONG
450
- // session (lastRegisteredKey fallback). The captured sessionId lets us
451
- // bypass that fallback and look up the correct taskId directly from
452
- // task-manager.
453
- const capturedA2ASessionId = getCurrentSessionContext()?.sessionId ?? null;
449
+ // wrapStreamFn is called per-agent (per session), ALS is still valid here.
450
+ // We capture the a2aSessionId and look up the store at each request time
451
+ // to always get the latest taskId (supports steer).
452
+ const alsContext = xyAsyncLocalStorage.getStore();
453
+ const capturedA2ASessionId = alsContext?.openclawSessionKey
454
+ ? getSession(alsContext.openclawSessionKey)?.a2aSessionId ?? null
455
+ : null;
454
456
  return async (model, context, options) => {
455
457
  // 每次请求时从 ctx.extraParams 动态读取 header
456
458
  const dynamicHeaders = {};
@@ -472,21 +474,11 @@ export const xiaoyiProvider = {
472
474
  }
473
475
  }
474
476
  else {
475
- // Session mode: resolve taskId for the correct session.
476
- //
477
- // Priority:
478
- // 1. capturedA2ASessionId → getCurrentTaskId() (most reliable,
479
- // bypasses lastRegisteredKey fallback)
480
- // 2. getCurrentSessionContext()?.taskId (works when ALS
481
- // is intact)
482
- // 3. ctx.extraParams cached values (last resort,
483
- // may be stale / from wrong session)
477
+ // Session mode: resolve taskId from store using captured a2aSessionId.
478
+ // This always returns the latest taskId (supports steer).
484
479
  let resolvedTaskId = null;
485
480
  if (capturedA2ASessionId) {
486
- resolvedTaskId = getCurrentTaskId(capturedA2ASessionId);
487
- }
488
- if (!resolvedTaskId) {
489
- resolvedTaskId = getCurrentSessionContext()?.taskId ?? null;
481
+ resolvedTaskId = getSessionByA2AId(capturedA2ASessionId)?.taskId ?? null;
490
482
  }
491
483
  const traceId = resolvedTaskId ?? ctx.extraParams[HEADER_TRACE_ID];
492
484
  const sessionId = resolvedTaskId?.split("&")[0]
@@ -517,12 +509,15 @@ export const xiaoyiProvider = {
517
509
  console.log(`[xiaoyiprovider] system prompt length: ${context.systemPrompt.length}`);
518
510
  }
519
511
  // Prefer deviceType from extraParams (set by prepareExtraParams).
520
- // Fall back to getCurrentSessionContext() because OpenClaw caches
512
+ // Fall back to store lookup because OpenClaw caches
521
513
  // resolvePreparedExtraParams by provider/modelId – the cache key does
522
514
  // not include session-specific data, so deviceType may be missing
523
515
  // from the cached extraParams even when a session is active.
524
516
  const extraParamsDeviceType = ctx.extraParams?.[DEVICE_TYPE_KEY] || undefined;
525
- const deviceType = extraParamsDeviceType ?? getCurrentSessionContext()?.deviceType;
517
+ const storeDeviceType = capturedA2ASessionId
518
+ ? getSessionByA2AId(capturedA2ASessionId)?.deviceType
519
+ : undefined;
520
+ const deviceType = extraParamsDeviceType ?? storeDeviceType;
526
521
  // 在发送给模型前,优化 systemPrompt 结构
527
522
  if (context.systemPrompt) {
528
523
  let sp = context.systemPrompt;
@@ -1,7 +1,7 @@
1
1
  import { getXYRuntime } from "./runtime.js";
2
2
  import { sendA2AResponse, sendStatusUpdate, sendReasoningTextUpdate } from "./formatter.js";
3
3
  import { resolveXYConfig } from "./config.js";
4
- import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
4
+ import { getSessionByA2AId } from "./xy-session-store.js";
5
5
  import fs from "fs/promises";
6
6
  import path from "path";
7
7
  import { logger } from "./utils/logger.js";
@@ -59,10 +59,12 @@ export function createXYReplyDispatcher(params) {
59
59
  * 每次需要taskId时,都从TaskManager获取最新值
60
60
  */
61
61
  const getActiveTaskId = () => {
62
- return getCurrentTaskId(sessionId) ?? initialTaskId;
62
+ const session = getSessionByA2AId(sessionId);
63
+ return session?.taskId ?? initialTaskId;
63
64
  };
64
65
  const getActiveMessageId = () => {
65
- return getCurrentMessageId(sessionId) ?? initialMessageId;
66
+ const session = getSessionByA2AId(sessionId);
67
+ return session?.messageId ?? initialMessageId;
66
68
  };
67
69
  const core = getXYRuntime();
68
70
  const config = resolveXYConfig(cfg);
@@ -1,6 +1,5 @@
1
1
  // Steer message injector for CSPL hook integration
2
- import { getSessionContext } from "./tools/session-manager.js";
3
- import { hasActiveTask, getCurrentTaskId } from "./task-manager.js";
2
+ import { getSession, hasActiveSession } from "./xy-session-store.js";
4
3
  import { handleXYMessage } from "./bot.js";
5
4
  import { logger } from "./utils/logger.js";
6
5
  import { randomUUID } from "node:crypto";
@@ -29,13 +28,12 @@ export async function tryInjectSteer(sessionKey, message) {
29
28
  if (!sessionKey) {
30
29
  return false;
31
30
  }
32
- const sessionCtx = getSessionContext(sessionKey);
33
- if (!sessionCtx) {
31
+ const session = getSession(sessionKey);
32
+ if (!session) {
34
33
  return false;
35
34
  }
36
- const { sessionId } = sessionCtx;
37
- const activeTaskId = getCurrentTaskId(sessionId);
38
- if (!hasActiveTask(sessionId)) {
35
+ const { a2aSessionId: sessionId, taskId: activeTaskId } = session;
36
+ if (!hasActiveSession(sessionKey)) {
39
37
  return false;
40
38
  }
41
39
  if (!cachedCfg || !cachedRuntime) {
@@ -1,7 +1,6 @@
1
- import type { SessionContext } from "./session-manager.js";
2
1
  /**
3
2
  * XY calendar event tool - creates a calendar event on user's device.
4
3
  * Requires title, dtStart (start time), and dtEnd (end time) parameters.
5
4
  * Time format must be: yyyy-mm-dd hh:mm:ss
6
5
  */
7
- export declare function createCalendarTool(ctx: SessionContext): any;
6
+ export declare function createCalendarTool(sessionKey: string): any;
@@ -1,12 +1,12 @@
1
1
  import { getXYWebSocketManager } from "../client.js";
2
2
  import { sendCommand } from "../formatter.js";
3
+ import { requireSession } from "./session-helper.js";
3
4
  /**
4
5
  * XY calendar event tool - creates a calendar event on user's device.
5
6
  * Requires title, dtStart (start time), and dtEnd (end time) parameters.
6
7
  * Time format must be: yyyy-mm-dd hh:mm:ss
7
8
  */
8
- export function createCalendarTool(ctx) {
9
- const { config, sessionId, taskId, messageId } = ctx;
9
+ export function createCalendarTool(sessionKey) {
10
10
  return {
11
11
  name: "create_calendar_event",
12
12
  label: "Create Calendar Event",
@@ -34,6 +34,7 @@ export function createCalendarTool(ctx) {
34
34
  required: ["title", "dtStart", "dtEnd"],
35
35
  },
36
36
  async execute(toolCallId, params) {
37
+ const session = requireSession(sessionKey);
37
38
  // Validate parameters
38
39
  if (!params.title || !params.dtStart || !params.dtEnd) {
39
40
  throw new Error("Missing required parameters: title, dtStart, and dtEnd are required");
@@ -45,7 +46,7 @@ export function createCalendarTool(ctx) {
45
46
  throw new Error("Invalid time format. Required format: yyyy-mm-dd hh:mm:ss (e.g., 2024-01-15 14:30:00)");
46
47
  }
47
48
  // Get WebSocket manager
48
- const wsManager = getXYWebSocketManager(config);
49
+ const wsManager = getXYWebSocketManager(session.config);
49
50
  // Build CreateCalendarEvent command
50
51
  const command = {
51
52
  header: {
@@ -111,10 +112,10 @@ export function createCalendarTool(ctx) {
111
112
  wsManager.on("data-event", handler);
112
113
  // Send the command
113
114
  sendCommand({
114
- config,
115
- sessionId,
116
- taskId,
117
- messageId,
115
+ config: session.config,
116
+ sessionId: session.a2aSessionId,
117
+ taskId: session.taskId,
118
+ messageId: session.messageId,
118
119
  command,
119
120
  })
120
121
  .then(() => {
@@ -1,6 +1,5 @@
1
- import type { SessionContext } from "./session-manager.js";
2
1
  /**
3
2
  * call_device_tool - 通用端工具调度器。
4
3
  * LLM 必须先通过 get_xxx_tool_schema 获取具体工具 schema,再用本工具执行。
5
4
  */
6
- export declare function createCallDeviceTool(ctx: SessionContext): any;
5
+ export declare function createCallDeviceTool(sessionKey: string): any;
@@ -23,37 +23,36 @@ import { createSaveFileToPhoneTool } from "./save-file-to-phone-tool.js";
23
23
  import { createSendEmailTool } from "./send-email-tool.js";
24
24
  import { createSearchEmailTool } from "./search-email-tool.js";
25
25
  import { sendStatusUpdate } from "../formatter.js";
26
- import { getCurrentTaskId, getCurrentMessageId } from "../task-manager.js";
26
+ import { requireSession } from "./session-helper.js";
27
27
  /**
28
28
  * call_device_tool - 通用端工具调度器。
29
29
  * LLM 必须先通过 get_xxx_tool_schema 获取具体工具 schema,再用本工具执行。
30
30
  */
31
- export function createCallDeviceTool(ctx) {
32
- const { config, sessionId, taskId, messageId } = ctx;
33
- const noteTool = createNoteTool(ctx);
34
- const modifyNoteTool = createModifyNoteTool(ctx);
35
- const createAlarmTool = makeAlarmTool(ctx);
36
- const modifyAlarmTool = createModifyAlarmTool(ctx);
37
- const deleteAlarmTool = createDeleteAlarmTool(ctx);
38
- const callPhoneTool = createCallPhoneTool(ctx);
39
- const calendarTool = createCalendarTool(ctx);
40
- const searchNoteTool = createSearchNoteTool(ctx);
41
- const searchMessageTool = createSearchMessageTool(ctx);
42
- const sendMessageTool = createSendMessageTool(ctx);
43
- const xiaoyiAddCollectionTool = createXiaoyiAddCollectionTool(ctx);
44
- const xiaoyiCollectionTool = createXiaoyiCollectionTool(ctx);
45
- const xiaoyiDeleteCollectionTool = createXiaoyiDeleteCollectionTool(ctx);
46
- const searchPhotoGalleryTool = createSearchPhotoGalleryTool(ctx);
47
- const uploadPhotoTool = createUploadPhotoTool(ctx);
48
- const uploadFileTool = createUploadFileTool(ctx);
49
- const sendEmailTool = createSendEmailTool(ctx);
50
- const searchAlarmTool = createSearchAlarmTool(ctx);
51
- const searchContactTool = createSearchContactTool(ctx);
52
- const searchCalendarTool = createSearchCalendarTool(ctx);
53
- const saveMediaToGalleryTool = createSaveMediaToGalleryTool(ctx);
54
- const searchFileTool = createSearchFileTool(ctx);
55
- const saveFileToPhoneTool = createSaveFileToPhoneTool(ctx);
56
- const searchEmailTool = createSearchEmailTool(ctx);
31
+ export function createCallDeviceTool(sessionKey) {
32
+ const noteTool = createNoteTool(sessionKey);
33
+ const modifyNoteTool = createModifyNoteTool(sessionKey);
34
+ const createAlarmTool = makeAlarmTool(sessionKey);
35
+ const modifyAlarmTool = createModifyAlarmTool(sessionKey);
36
+ const deleteAlarmTool = createDeleteAlarmTool(sessionKey);
37
+ const callPhoneTool = createCallPhoneTool(sessionKey);
38
+ const calendarTool = createCalendarTool(sessionKey);
39
+ const searchNoteTool = createSearchNoteTool(sessionKey);
40
+ const searchMessageTool = createSearchMessageTool(sessionKey);
41
+ const sendMessageTool = createSendMessageTool(sessionKey);
42
+ const xiaoyiAddCollectionTool = createXiaoyiAddCollectionTool(sessionKey);
43
+ const xiaoyiCollectionTool = createXiaoyiCollectionTool(sessionKey);
44
+ const xiaoyiDeleteCollectionTool = createXiaoyiDeleteCollectionTool(sessionKey);
45
+ const searchPhotoGalleryTool = createSearchPhotoGalleryTool(sessionKey);
46
+ const uploadPhotoTool = createUploadPhotoTool(sessionKey);
47
+ const uploadFileTool = createUploadFileTool(sessionKey);
48
+ const sendEmailTool = createSendEmailTool(sessionKey);
49
+ const searchAlarmTool = createSearchAlarmTool(sessionKey);
50
+ const searchContactTool = createSearchContactTool(sessionKey);
51
+ const searchCalendarTool = createSearchCalendarTool(sessionKey);
52
+ const saveMediaToGalleryTool = createSaveMediaToGalleryTool(sessionKey);
53
+ const searchFileTool = createSearchFileTool(sessionKey);
54
+ const saveFileToPhoneTool = createSaveFileToPhoneTool(sessionKey);
55
+ const searchEmailTool = createSearchEmailTool(sessionKey);
57
56
  /**
58
57
  * 端工具注册表 —— 按 name 索引所有可通过 call_device_tool 调度的工具。
59
58
  */
@@ -102,14 +101,15 @@ export function createCallDeviceTool(ctx) {
102
101
  required: ["toolName", "arguments"],
103
102
  },
104
103
  async execute(toolCallId, params) {
104
+ const session = requireSession(sessionKey);
105
105
  const { toolName, arguments: toolArgs } = params;
106
106
  // 向用户端发送具体工具名的状态更新
107
- const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
108
- const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
107
+ const currentTaskId = session.taskId;
108
+ const currentMessageId = session.messageId;
109
109
  try {
110
110
  await sendStatusUpdate({
111
- config,
112
- sessionId,
111
+ config: session.config,
112
+ sessionId: session.a2aSessionId,
113
113
  taskId: currentTaskId,
114
114
  messageId: currentMessageId,
115
115
  text: `正在使用工具: ${toolName}...`,
@@ -1,6 +1,5 @@
1
- import type { SessionContext } from "./session-manager.js";
2
1
  /**
3
2
  * XY call phone tool - makes a phone call on user's device.
4
3
  * Requires phoneNumber parameter and optional slotId (0 for primary SIM, 1 for secondary SIM).
5
4
  */
6
- export declare function createCallPhoneTool(ctx: SessionContext): any;
5
+ export declare function createCallPhoneTool(sessionKey: string): any;