@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.
- package/dist/src/bot.js +27 -60
- package/dist/src/channel.js +13 -5
- package/dist/src/formatter.js +5 -5
- package/dist/src/monitor.js +12 -12
- package/dist/src/outbound.js +5 -5
- package/dist/src/provider.js +23 -28
- package/dist/src/reply-dispatcher.js +5 -3
- package/dist/src/steer-injector.js +5 -7
- package/dist/src/tools/calendar-tool.d.ts +1 -2
- package/dist/src/tools/calendar-tool.js +8 -7
- package/dist/src/tools/call-device-tool.d.ts +1 -2
- package/dist/src/tools/call-device-tool.js +31 -31
- package/dist/src/tools/call-phone-tool.d.ts +1 -2
- package/dist/src/tools/call-phone-tool.js +8 -7
- package/dist/src/tools/create-alarm-tool.d.ts +1 -2
- package/dist/src/tools/create-alarm-tool.js +8 -7
- package/dist/src/tools/create-all-tools.d.ts +7 -8
- package/dist/src/tools/create-all-tools.js +20 -24
- package/dist/src/tools/delete-alarm-tool.d.ts +1 -2
- package/dist/src/tools/delete-alarm-tool.js +8 -7
- package/dist/src/tools/get-alarm-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-alarm-tool-schema.js +5 -5
- package/dist/src/tools/get-calendar-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-calendar-tool-schema.js +3 -3
- package/dist/src/tools/get-collection-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-collection-tool-schema.js +2 -2
- package/dist/src/tools/get-contact-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-contact-tool-schema.js +5 -5
- package/dist/src/tools/get-device-file-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-device-file-tool-schema.js +4 -4
- package/dist/src/tools/get-email-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-email-tool-schema.js +3 -3
- package/dist/src/tools/get-note-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-note-tool-schema.js +4 -4
- package/dist/src/tools/get-photo-tool-schema.d.ts +1 -2
- package/dist/src/tools/get-photo-tool-schema.js +3 -3
- package/dist/src/tools/image-reading-tool.d.ts +1 -2
- package/dist/src/tools/image-reading-tool.js +5 -4
- package/dist/src/tools/location-tool.d.ts +1 -2
- package/dist/src/tools/location-tool.js +8 -7
- package/dist/src/tools/login-token-tool.d.ts +1 -2
- package/dist/src/tools/login-token-tool.js +9 -9
- package/dist/src/tools/modify-alarm-tool.d.ts +1 -2
- package/dist/src/tools/modify-alarm-tool.js +8 -7
- package/dist/src/tools/modify-note-tool.d.ts +1 -2
- package/dist/src/tools/modify-note-tool.js +8 -7
- package/dist/src/tools/note-tool.d.ts +1 -2
- package/dist/src/tools/note-tool.js +8 -7
- package/dist/src/tools/query-app-message-tool.d.ts +1 -2
- package/dist/src/tools/query-app-message-tool.js +8 -7
- package/dist/src/tools/query-memory-data-tool.d.ts +1 -2
- package/dist/src/tools/query-memory-data-tool.js +8 -7
- package/dist/src/tools/query-todo-task-tool.d.ts +1 -2
- package/dist/src/tools/query-todo-task-tool.js +8 -7
- package/dist/src/tools/save-file-to-phone-tool.d.ts +1 -2
- package/dist/src/tools/save-file-to-phone-tool.js +9 -8
- package/dist/src/tools/save-media-to-gallery-tool.d.ts +1 -2
- package/dist/src/tools/save-media-to-gallery-tool.js +9 -8
- package/dist/src/tools/save-self-evolution-skill-tool.d.ts +1 -2
- package/dist/src/tools/save-self-evolution-skill-tool.js +4 -3
- package/dist/src/tools/search-alarm-tool.d.ts +1 -2
- package/dist/src/tools/search-alarm-tool.js +8 -7
- package/dist/src/tools/search-calendar-tool.d.ts +1 -2
- package/dist/src/tools/search-calendar-tool.js +8 -7
- package/dist/src/tools/search-contact-tool.d.ts +1 -2
- package/dist/src/tools/search-contact-tool.js +8 -7
- package/dist/src/tools/search-email-tool.d.ts +1 -2
- package/dist/src/tools/search-email-tool.js +8 -7
- package/dist/src/tools/search-file-tool.d.ts +1 -2
- package/dist/src/tools/search-file-tool.js +8 -7
- package/dist/src/tools/search-message-tool.d.ts +1 -2
- package/dist/src/tools/search-message-tool.js +8 -7
- package/dist/src/tools/search-note-tool.d.ts +1 -2
- package/dist/src/tools/search-note-tool.js +8 -7
- package/dist/src/tools/search-photo-gallery-tool.d.ts +1 -2
- package/dist/src/tools/search-photo-gallery-tool.js +5 -4
- package/dist/src/tools/send-email-tool.d.ts +1 -2
- package/dist/src/tools/send-email-tool.js +8 -7
- package/dist/src/tools/send-file-to-user-tool.d.ts +1 -2
- package/dist/src/tools/send-file-to-user-tool.js +11 -12
- package/dist/src/tools/send-message-tool.d.ts +1 -2
- package/dist/src/tools/send-message-tool.js +8 -7
- package/dist/src/tools/session-helper.d.ts +13 -0
- package/dist/src/tools/session-helper.js +19 -0
- package/dist/src/tools/upload-file-tool.d.ts +1 -2
- package/dist/src/tools/upload-file-tool.js +5 -4
- package/dist/src/tools/upload-photo-tool.d.ts +1 -2
- package/dist/src/tools/upload-photo-tool.js +5 -4
- package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +1 -2
- package/dist/src/tools/xiaoyi-add-collection-tool.js +9 -8
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +1 -2
- package/dist/src/tools/xiaoyi-collection-tool.js +8 -7
- package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +1 -2
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +8 -7
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +1 -2
- package/dist/src/tools/xiaoyi-gui-tool.js +10 -10
- package/dist/src/xy-session-store.d.ts +79 -0
- package/dist/src/xy-session-store.js +153 -0
- 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 {
|
|
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 &&
|
|
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
|
-
//
|
|
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
|
-
|
|
151
|
+
a2aSessionId: parsed.sessionId,
|
|
163
152
|
taskId: parsed.taskId,
|
|
164
153
|
messageId: parsed.messageId,
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
// 🔐
|
|
294
|
-
|
|
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
|
-
// 🔑 错误时也要清理
|
|
301
|
+
// 🔑 错误时也要清理session
|
|
330
302
|
try {
|
|
331
|
-
const
|
|
332
|
-
const 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
|
-
|
|
336
|
-
|
|
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(
|
|
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
|
}
|
package/dist/src/channel.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
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: {
|
package/dist/src/formatter.js
CHANGED
|
@@ -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 {
|
|
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 =
|
|
118
|
-
const currentMessageId =
|
|
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 =
|
|
166
|
-
const currentMessageId =
|
|
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 = {
|
package/dist/src/monitor.js
CHANGED
|
@@ -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 {
|
|
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 "./
|
|
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 =
|
|
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
|
|
218
|
-
if (
|
|
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 ${
|
|
222
|
-
const sendPromises =
|
|
221
|
+
log(`[MONITOR] 📤 Sending restart notifications to ${activeSessions.length} active session(s)`);
|
|
222
|
+
const sendPromises = activeSessions.map(({ session }) => sendA2AResponse({
|
|
223
223
|
config,
|
|
224
|
-
sessionId:
|
|
225
|
-
taskId:
|
|
226
|
-
messageId:
|
|
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 ${
|
|
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 ${
|
|
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`);
|
package/dist/src/outbound.js
CHANGED
|
@@ -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 {
|
|
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
|
|
73
|
-
if (
|
|
74
|
-
const enhancedTarget = `${trimmedTo}::${
|
|
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,
|
package/dist/src/provider.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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]:
|
|
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
|
-
//
|
|
448
|
-
// the
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
62
|
+
const session = getSessionByA2AId(sessionId);
|
|
63
|
+
return session?.taskId ?? initialTaskId;
|
|
63
64
|
};
|
|
64
65
|
const getActiveMessageId = () => {
|
|
65
|
-
|
|
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 {
|
|
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
|
|
33
|
-
if (!
|
|
31
|
+
const session = getSession(sessionKey);
|
|
32
|
+
if (!session) {
|
|
34
33
|
return false;
|
|
35
34
|
}
|
|
36
|
-
const { sessionId } =
|
|
37
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 {
|
|
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(
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
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 =
|
|
108
|
-
const currentMessageId =
|
|
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(
|
|
5
|
+
export declare function createCallPhoneTool(sessionKey: string): any;
|