@ynhcj/xiaoyi-channel 0.0.107-next → 0.0.108-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 +64 -31
- package/dist/src/channel.js +5 -15
- 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 +39 -75
- package/dist/src/reply-dispatcher.js +3 -5
- package/dist/src/steer-injector.js +7 -5
- package/dist/src/tools/calendar-tool.d.ts +2 -1
- package/dist/src/tools/calendar-tool.js +7 -8
- package/dist/src/tools/call-device-tool.d.ts +2 -1
- package/dist/src/tools/call-device-tool.js +31 -31
- package/dist/src/tools/call-phone-tool.d.ts +2 -1
- package/dist/src/tools/call-phone-tool.js +7 -8
- package/dist/src/tools/create-alarm-tool.d.ts +2 -1
- package/dist/src/tools/create-alarm-tool.js +7 -8
- package/dist/src/tools/create-all-tools.d.ts +8 -7
- package/dist/src/tools/create-all-tools.js +24 -20
- package/dist/src/tools/delete-alarm-tool.d.ts +2 -1
- package/dist/src/tools/delete-alarm-tool.js +7 -8
- package/dist/src/tools/get-alarm-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-alarm-tool-schema.js +5 -5
- package/dist/src/tools/get-calendar-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-calendar-tool-schema.js +3 -3
- package/dist/src/tools/get-collection-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-collection-tool-schema.js +2 -2
- package/dist/src/tools/get-contact-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-contact-tool-schema.js +5 -5
- package/dist/src/tools/get-device-file-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-device-file-tool-schema.js +4 -4
- package/dist/src/tools/get-email-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-email-tool-schema.js +3 -3
- package/dist/src/tools/get-note-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-note-tool-schema.js +4 -4
- package/dist/src/tools/get-photo-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-photo-tool-schema.js +3 -3
- package/dist/src/tools/image-reading-tool.d.ts +2 -1
- package/dist/src/tools/image-reading-tool.js +4 -5
- package/dist/src/tools/location-tool.d.ts +2 -1
- package/dist/src/tools/location-tool.js +7 -8
- package/dist/src/tools/login-token-tool.d.ts +2 -1
- package/dist/src/tools/login-token-tool.js +9 -9
- package/dist/src/tools/modify-alarm-tool.d.ts +2 -1
- package/dist/src/tools/modify-alarm-tool.js +7 -8
- package/dist/src/tools/modify-note-tool.d.ts +2 -1
- package/dist/src/tools/modify-note-tool.js +7 -8
- package/dist/src/tools/note-tool.d.ts +2 -1
- package/dist/src/tools/note-tool.js +7 -8
- package/dist/src/tools/query-app-message-tool.d.ts +2 -1
- package/dist/src/tools/query-app-message-tool.js +7 -8
- package/dist/src/tools/query-memory-data-tool.d.ts +2 -1
- package/dist/src/tools/query-memory-data-tool.js +7 -8
- package/dist/src/tools/query-todo-task-tool.d.ts +2 -1
- package/dist/src/tools/query-todo-task-tool.js +7 -8
- package/dist/src/tools/save-file-to-phone-tool.d.ts +2 -1
- package/dist/src/tools/save-file-to-phone-tool.js +8 -9
- package/dist/src/tools/save-media-to-gallery-tool.d.ts +2 -1
- package/dist/src/tools/save-media-to-gallery-tool.js +8 -9
- package/dist/src/tools/save-self-evolution-skill-tool.d.ts +2 -1
- package/dist/src/tools/save-self-evolution-skill-tool.js +3 -4
- package/dist/src/tools/search-alarm-tool.d.ts +2 -1
- package/dist/src/tools/search-alarm-tool.js +7 -8
- package/dist/src/tools/search-calendar-tool.d.ts +2 -1
- package/dist/src/tools/search-calendar-tool.js +7 -8
- package/dist/src/tools/search-contact-tool.d.ts +2 -1
- package/dist/src/tools/search-contact-tool.js +7 -8
- package/dist/src/tools/search-email-tool.d.ts +2 -1
- package/dist/src/tools/search-email-tool.js +7 -8
- package/dist/src/tools/search-file-tool.d.ts +2 -1
- package/dist/src/tools/search-file-tool.js +7 -8
- package/dist/src/tools/search-message-tool.d.ts +2 -1
- package/dist/src/tools/search-message-tool.js +7 -8
- package/dist/src/tools/search-note-tool.d.ts +2 -1
- package/dist/src/tools/search-note-tool.js +7 -8
- package/dist/src/tools/search-photo-gallery-tool.d.ts +2 -1
- package/dist/src/tools/search-photo-gallery-tool.js +4 -5
- package/dist/src/tools/send-email-tool.d.ts +2 -1
- package/dist/src/tools/send-email-tool.js +7 -8
- package/dist/src/tools/send-file-to-user-tool.d.ts +2 -1
- package/dist/src/tools/send-file-to-user-tool.js +12 -11
- package/dist/src/tools/send-message-tool.d.ts +2 -1
- package/dist/src/tools/send-message-tool.js +7 -8
- package/dist/src/tools/upload-file-tool.d.ts +2 -1
- package/dist/src/tools/upload-file-tool.js +4 -5
- package/dist/src/tools/upload-photo-tool.d.ts +2 -1
- package/dist/src/tools/upload-photo-tool.js +4 -5
- package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-add-collection-tool.js +8 -9
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-collection-tool.js +7 -8
- package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +7 -8
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-gui-tool.js +10 -10
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -6,13 +6,14 @@ 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";
|
|
9
10
|
import { configManager } from "./utils/config-manager.js";
|
|
10
11
|
import { addPushId } from "./utils/pushid-manager.js";
|
|
11
12
|
import { getPushDataById } from "./utils/pushdata-manager.js";
|
|
12
13
|
import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
|
|
13
14
|
import { saveRuntimeInfo } from "./utils/runtime-manager.js";
|
|
14
15
|
import { toolCallNudgeManager } from "./utils/tool-call-nudge-manager.js";
|
|
15
|
-
import {
|
|
16
|
+
import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActiveTask, } from "./task-manager.js";
|
|
16
17
|
/**
|
|
17
18
|
* Handle an incoming A2A message.
|
|
18
19
|
* This is the main entry point for message processing.
|
|
@@ -100,26 +101,22 @@ export async function handleXYMessage(params) {
|
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
// ========================================
|
|
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}`);
|
|
104
|
+
// 🔑 检测steer模式和是否是第二条消息
|
|
116
105
|
const isSteerMode = cfg.messages?.queue?.mode === "steer";
|
|
117
|
-
const isSecondMessage = isSteerMode &&
|
|
106
|
+
const isSecondMessage = isSteerMode && hasActiveTask(parsed.sessionId);
|
|
118
107
|
if (isSecondMessage) {
|
|
119
108
|
log(`[BOT] 🔄 STEER MODE - Second message detected (will be follower)`);
|
|
120
109
|
log(`[BOT] - Session: ${parsed.sessionId}`);
|
|
121
110
|
log(`[BOT] - New taskId: ${parsed.taskId} (will replace current)`);
|
|
122
111
|
}
|
|
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
|
+
}
|
|
123
120
|
// Extract and update push_id if present
|
|
124
121
|
const pushId = extractPushId(parsed.parts);
|
|
125
122
|
if (pushId) {
|
|
@@ -145,13 +142,27 @@ export async function handleXYMessage(params) {
|
|
|
145
142
|
).catch((err) => {
|
|
146
143
|
error(`[BOT] Failed to save runtime info:`, err);
|
|
147
144
|
});
|
|
148
|
-
//
|
|
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}`);
|
|
149
160
|
registerSession(route.sessionKey, {
|
|
150
161
|
config,
|
|
151
|
-
|
|
162
|
+
sessionId: parsed.sessionId,
|
|
152
163
|
taskId: parsed.taskId,
|
|
153
164
|
messageId: parsed.messageId,
|
|
154
|
-
|
|
165
|
+
agentId: route.accountId,
|
|
155
166
|
deviceType,
|
|
156
167
|
});
|
|
157
168
|
// 🔑 发送初始状态更新(第二条消息也要发,用新taskId)
|
|
@@ -252,18 +263,35 @@ export async function handleXYMessage(params) {
|
|
|
252
263
|
startStatusInterval();
|
|
253
264
|
log(`[BOT-DISPATCHER] ✅ Status interval started for first message`);
|
|
254
265
|
}
|
|
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
|
+
};
|
|
255
275
|
log(`[BOT-DISPATCH] ⏳ withReplyDispatcher starting, sessionKey=${route.sessionKey}`);
|
|
256
|
-
|
|
257
|
-
// during dispatcher setup (which happens before run() is called).
|
|
258
|
-
await xyAsyncLocalStorage.run({ openclawSessionKey: route.sessionKey }, () => core.channel.reply.withReplyDispatcher({
|
|
276
|
+
await core.channel.reply.withReplyDispatcher({
|
|
259
277
|
dispatcher,
|
|
260
278
|
onSettled: () => {
|
|
261
279
|
log(`[BOT] 🏁 onSettled called for session: ${route.sessionKey}`);
|
|
262
|
-
|
|
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引用计数
|
|
263
289
|
unregisterSession(route.sessionKey);
|
|
264
290
|
log(`[BOT] ✅ Cleanup completed`);
|
|
265
291
|
},
|
|
266
|
-
run:
|
|
292
|
+
run: () =>
|
|
293
|
+
// 🔐 Use AsyncLocalStorage to provide session context to tools
|
|
294
|
+
runWithSessionContext(sessionContext, async () => {
|
|
267
295
|
log(`[BOT-DISPATCH] ⏳ dispatchReplyFromConfig starting...`);
|
|
268
296
|
log(`[BOT-DISPATCH] - sessionKey: ${ctxPayload.SessionKey}`);
|
|
269
297
|
log(`[BOT-DISPATCH] - provider: ${ctxPayload.Provider}`);
|
|
@@ -288,8 +316,8 @@ export async function handleXYMessage(params) {
|
|
|
288
316
|
error(`[BOT-DISPATCH] - error stack: ${dispatchErr instanceof Error ? dispatchErr.stack?.slice(0, 500) : "N/A"}`);
|
|
289
317
|
throw dispatchErr;
|
|
290
318
|
}
|
|
291
|
-
},
|
|
292
|
-
})
|
|
319
|
+
}),
|
|
320
|
+
});
|
|
293
321
|
log(`[BOT] ✅ Dispatcher completed for session: ${parsed.sessionId}`);
|
|
294
322
|
log(`xy: dispatch complete (session=${parsed.sessionId})`);
|
|
295
323
|
}
|
|
@@ -298,14 +326,18 @@ export async function handleXYMessage(params) {
|
|
|
298
326
|
error("Failed to handle XY message:", err);
|
|
299
327
|
runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
|
|
300
328
|
log(`[BOT] ❌ Error occurred, attempting cleanup...`);
|
|
301
|
-
// 🔑 错误时也要清理session
|
|
329
|
+
// 🔑 错误时也要清理taskId和session
|
|
302
330
|
try {
|
|
303
|
-
const
|
|
304
|
-
const sessionId =
|
|
331
|
+
const params = message.params;
|
|
332
|
+
const sessionId = params?.sessionId;
|
|
305
333
|
if (sessionId) {
|
|
306
334
|
log(`[BOT] 🧹 Cleaning up after error: ${sessionId}`);
|
|
307
|
-
|
|
308
|
-
|
|
335
|
+
// 清理 taskId
|
|
336
|
+
decrementTaskIdRef(sessionId);
|
|
337
|
+
unlockTaskId(sessionId);
|
|
338
|
+
// 清理 session
|
|
339
|
+
const core = getXYRuntime();
|
|
340
|
+
const route = core.channel.routing.resolveAgentRoute({
|
|
309
341
|
cfg,
|
|
310
342
|
channel: "xiaoyi-channel",
|
|
311
343
|
accountId,
|
|
@@ -314,12 +346,13 @@ export async function handleXYMessage(params) {
|
|
|
314
346
|
id: sessionId,
|
|
315
347
|
},
|
|
316
348
|
});
|
|
317
|
-
unregisterSession(
|
|
349
|
+
unregisterSession(route.sessionKey);
|
|
318
350
|
log(`[BOT] ✅ Cleanup completed after error`);
|
|
319
351
|
}
|
|
320
352
|
}
|
|
321
353
|
catch (cleanupErr) {
|
|
322
354
|
log(`[BOT] ⚠️ Cleanup failed:`, cleanupErr);
|
|
355
|
+
// Ignore cleanup errors
|
|
323
356
|
}
|
|
324
357
|
// ❌ Don't re-throw: message processing error should not affect gateway stability
|
|
325
358
|
}
|
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 { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
6
6
|
import { createAllTools } from "./tools/create-all-tools.js";
|
|
7
7
|
import { logger } from "./utils/logger.js";
|
|
8
8
|
/**
|
|
@@ -44,20 +44,10 @@ export const xyPlugin = {
|
|
|
44
44
|
},
|
|
45
45
|
outbound: xyOutbound,
|
|
46
46
|
agentTools: () => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const sessionKey = alsContext?.openclawSessionKey;
|
|
52
|
-
const allTools = createAllTools(sessionKey ?? undefined);
|
|
53
|
-
const session = sessionKey ? getSession(sessionKey) : null;
|
|
54
|
-
const filtered = filterToolsByDevice(allTools, session?.deviceType);
|
|
55
|
-
if (sessionKey) {
|
|
56
|
-
logger.log(`[CHANNEL-TOOLS] sessionKey=${sessionKey} deviceType=${session?.deviceType ?? "(none)"}, tools: ${allTools.length} → ${filtered.length}`);
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
logger.log(`[CHANNEL-TOOLS] no ALS at factory time, created ${filtered.length} tools (will resolve session at execute time)`);
|
|
60
|
-
}
|
|
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(", ")})`);
|
|
61
51
|
return filtered;
|
|
62
52
|
},
|
|
63
53
|
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 { getCurrentTaskId, getCurrentMessageId } from "./task-manager.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 = getCurrentTaskId(sessionId) ?? taskId;
|
|
118
|
+
const currentMessageId = getCurrentMessageId(sessionId) ?? 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 = getCurrentTaskId(sessionId) ?? taskId;
|
|
166
|
+
const currentMessageId = getCurrentMessageId(sessionId) ?? 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 { hasActiveTask, getAllActiveTaskBindings } from "./task-manager.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 "./tools/session-manager.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 = hasActiveTask(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 activeBindings = getAllActiveTaskBindings();
|
|
218
|
+
if (activeBindings.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 ${activeBindings.length} active session(s)`);
|
|
222
|
+
const sendPromises = activeBindings.map(binding => sendA2AResponse({
|
|
223
223
|
config,
|
|
224
|
-
sessionId:
|
|
225
|
-
taskId:
|
|
226
|
-
messageId:
|
|
224
|
+
sessionId: binding.sessionId,
|
|
225
|
+
taskId: binding.currentTaskId,
|
|
226
|
+
messageId: binding.currentMessageId,
|
|
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 ${binding.sessionId}: ${String(err)}`);
|
|
233
233
|
}));
|
|
234
234
|
await Promise.all(sendPromises);
|
|
235
|
-
log(`[MONITOR] ✅ Restart notifications sent to ${
|
|
235
|
+
log(`[MONITOR] ✅ Restart notifications sent to ${activeBindings.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 { getCurrentSessionContext } from "./tools/session-manager.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
|
|
72
|
+
const sessionContext = getCurrentSessionContext();
|
|
73
|
+
if (sessionContext && sessionContext.sessionId === trimmedTo) {
|
|
74
|
+
const enhancedTarget = `${trimmedTo}::${sessionContext.taskId}`;
|
|
75
75
|
logger.log(`[xyOutbound.resolveTarget] Enhanced target: ${enhancedTarget}`);
|
|
76
76
|
return {
|
|
77
77
|
ok: true,
|
package/dist/src/provider.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// models.providers.xiaoyiprovider.api = "openai-completions"
|
|
9
9
|
// models.providers.xiaoyiprovider.models = [...]
|
|
10
10
|
import { createHash } from "crypto";
|
|
11
|
-
import {
|
|
11
|
+
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
12
|
+
import { getCurrentTaskId } from "./task-manager.js";
|
|
12
13
|
import { selfEvolutionManager } from "./utils/self-evolution-manager.js";
|
|
13
14
|
// ── Retry config ──────────────────────────────────────────────
|
|
14
15
|
const RETRY_DELAYS_MS = [10_000, 20_000, 40_000, 60_000, 60_000];
|
|
@@ -404,12 +405,9 @@ export const xiaoyiProvider = {
|
|
|
404
405
|
* 3. No uid available → return undefined (no headers injected)
|
|
405
406
|
*/
|
|
406
407
|
prepareExtraParams: (ctx) => {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const session = sessionKey ? getSession(sessionKey) : null;
|
|
411
|
-
if (session) {
|
|
412
|
-
const taskId = session.taskId;
|
|
408
|
+
const sessionCtx = getCurrentSessionContext();
|
|
409
|
+
if (sessionCtx) {
|
|
410
|
+
const taskId = sessionCtx.taskId;
|
|
413
411
|
const sessionId = taskId.split("&")[0];
|
|
414
412
|
const interactionId = taskId.split("&")[1] || "";
|
|
415
413
|
return {
|
|
@@ -417,7 +415,7 @@ export const xiaoyiProvider = {
|
|
|
417
415
|
[HEADER_TRACE_ID]: taskId,
|
|
418
416
|
[HEADER_SESSION_ID]: sessionId,
|
|
419
417
|
[HEADER_INTERACTION_ID]: interactionId,
|
|
420
|
-
[DEVICE_TYPE_KEY]:
|
|
418
|
+
[DEVICE_TYPE_KEY]: sessionCtx.deviceType ?? "",
|
|
421
419
|
};
|
|
422
420
|
}
|
|
423
421
|
// Fallback: store uid prefix for lazy timestamp generation in wrapStreamFn.
|
|
@@ -446,54 +444,17 @@ export const xiaoyiProvider = {
|
|
|
446
444
|
if (!underlying)
|
|
447
445
|
return underlying;
|
|
448
446
|
// Capture A2A sessionId at agent setup time for multi-session isolation.
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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;
|
|
456
454
|
return async (model, context, options) => {
|
|
455
|
+
// 每次请求时从 ctx.extraParams 动态读取 header
|
|
457
456
|
const dynamicHeaders = {};
|
|
458
|
-
|
|
459
|
-
// 1. capturedA2ASessionId → store lookup (set at wrapStreamFn setup if ALS was active)
|
|
460
|
-
// 2. ALS → store lookup (works at request time if async chain preserved context)
|
|
461
|
-
// 3. Store enumeration (safe when only one session is active)
|
|
462
|
-
let resolvedTaskId = null;
|
|
463
|
-
if (capturedA2ASessionId) {
|
|
464
|
-
resolvedTaskId = getSessionByA2AId(capturedA2ASessionId)?.taskId ?? null;
|
|
465
|
-
}
|
|
466
|
-
if (!resolvedTaskId) {
|
|
467
|
-
const alsContext = xyAsyncLocalStorage.getStore();
|
|
468
|
-
const alsKey = alsContext?.openclawSessionKey;
|
|
469
|
-
if (alsKey) {
|
|
470
|
-
resolvedTaskId = getSession(alsKey)?.taskId ?? null;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
if (!resolvedTaskId) {
|
|
474
|
-
const allSessions = getAllActiveSessions();
|
|
475
|
-
if (allSessions.length === 1) {
|
|
476
|
-
resolvedTaskId = allSessions[0].session.taskId;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
if (resolvedTaskId) {
|
|
480
|
-
// Session mode: use taskId from store (supports steer).
|
|
481
|
-
const sessionId = resolvedTaskId.split("&")[0];
|
|
482
|
-
const interactionId = resolvedTaskId.split("&")[1] || "";
|
|
483
|
-
const isCron = isCronTriggered(context.messages);
|
|
484
|
-
dynamicHeaders[HEADER_TRACE_ID] = isCron ? `cron_${resolvedTaskId}_${Date.now()}` : resolvedTaskId;
|
|
485
|
-
dynamicHeaders[HEADER_SESSION_ID] = sessionId;
|
|
486
|
-
dynamicHeaders[HEADER_INTERACTION_ID] = interactionId;
|
|
487
|
-
if (isCron) {
|
|
488
|
-
const cronTitle = extractCronTitle(context.messages);
|
|
489
|
-
if (cronTitle)
|
|
490
|
-
dynamicHeaders["x-cron-title"] = encodeURIComponent(cronTitle);
|
|
491
|
-
if (context.messages?.length === 1)
|
|
492
|
-
dynamicHeaders["x-cron-flag"] = "begin";
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
else if (ctx.extraParams) {
|
|
496
|
-
// No active session — fall back to prepareExtraParams cache.
|
|
457
|
+
if (ctx.extraParams) {
|
|
497
458
|
const fallbackPrefix = ctx.extraParams[FALLBACK_PREFIX_KEY];
|
|
498
459
|
if (typeof fallbackPrefix === "string") {
|
|
499
460
|
// Fallback mode: generate fresh timestamp per request
|
|
@@ -511,10 +472,28 @@ export const xiaoyiProvider = {
|
|
|
511
472
|
}
|
|
512
473
|
}
|
|
513
474
|
else {
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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)
|
|
484
|
+
let resolvedTaskId = null;
|
|
485
|
+
if (capturedA2ASessionId) {
|
|
486
|
+
resolvedTaskId = getCurrentTaskId(capturedA2ASessionId);
|
|
487
|
+
}
|
|
488
|
+
if (!resolvedTaskId) {
|
|
489
|
+
resolvedTaskId = getCurrentSessionContext()?.taskId ?? null;
|
|
490
|
+
}
|
|
491
|
+
const traceId = resolvedTaskId ?? ctx.extraParams[HEADER_TRACE_ID];
|
|
492
|
+
const sessionId = resolvedTaskId?.split("&")[0]
|
|
493
|
+
?? ctx.extraParams[HEADER_SESSION_ID];
|
|
494
|
+
const interactionId = resolvedTaskId?.split("&")[1]
|
|
495
|
+
?? ctx.extraParams[HEADER_INTERACTION_ID]
|
|
496
|
+
?? "";
|
|
518
497
|
if (typeof traceId === "string") {
|
|
519
498
|
const isCron = isCronTriggered(context.messages);
|
|
520
499
|
dynamicHeaders[HEADER_TRACE_ID] = isCron ? `cron_${traceId}_${Date.now()}` : traceId;
|
|
@@ -538,27 +517,12 @@ export const xiaoyiProvider = {
|
|
|
538
517
|
console.log(`[xiaoyiprovider] system prompt length: ${context.systemPrompt.length}`);
|
|
539
518
|
}
|
|
540
519
|
// Prefer deviceType from extraParams (set by prepareExtraParams).
|
|
541
|
-
// Fall back to
|
|
520
|
+
// Fall back to getCurrentSessionContext() because OpenClaw caches
|
|
542
521
|
// resolvePreparedExtraParams by provider/modelId – the cache key does
|
|
543
522
|
// not include session-specific data, so deviceType may be missing
|
|
544
523
|
// from the cached extraParams even when a session is active.
|
|
545
524
|
const extraParamsDeviceType = ctx.extraParams?.[DEVICE_TYPE_KEY] || undefined;
|
|
546
|
-
|
|
547
|
-
if (capturedA2ASessionId) {
|
|
548
|
-
storeDeviceType = getSessionByA2AId(capturedA2ASessionId)?.deviceType;
|
|
549
|
-
}
|
|
550
|
-
if (!storeDeviceType) {
|
|
551
|
-
const alsCtx = xyAsyncLocalStorage.getStore();
|
|
552
|
-
const key = alsCtx?.openclawSessionKey;
|
|
553
|
-
if (key)
|
|
554
|
-
storeDeviceType = getSession(key)?.deviceType;
|
|
555
|
-
}
|
|
556
|
-
if (!storeDeviceType) {
|
|
557
|
-
const all = getAllActiveSessions();
|
|
558
|
-
if (all.length === 1)
|
|
559
|
-
storeDeviceType = all[0].session.deviceType;
|
|
560
|
-
}
|
|
561
|
-
const deviceType = extraParamsDeviceType ?? storeDeviceType;
|
|
525
|
+
const deviceType = extraParamsDeviceType ?? getCurrentSessionContext()?.deviceType;
|
|
562
526
|
// 在发送给模型前,优化 systemPrompt 结构
|
|
563
527
|
if (context.systemPrompt) {
|
|
564
528
|
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 { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
5
5
|
import fs from "fs/promises";
|
|
6
6
|
import path from "path";
|
|
7
7
|
import { logger } from "./utils/logger.js";
|
|
@@ -59,12 +59,10 @@ export function createXYReplyDispatcher(params) {
|
|
|
59
59
|
* 每次需要taskId时,都从TaskManager获取最新值
|
|
60
60
|
*/
|
|
61
61
|
const getActiveTaskId = () => {
|
|
62
|
-
|
|
63
|
-
return session?.taskId ?? initialTaskId;
|
|
62
|
+
return getCurrentTaskId(sessionId) ?? initialTaskId;
|
|
64
63
|
};
|
|
65
64
|
const getActiveMessageId = () => {
|
|
66
|
-
|
|
67
|
-
return session?.messageId ?? initialMessageId;
|
|
65
|
+
return getCurrentMessageId(sessionId) ?? initialMessageId;
|
|
68
66
|
};
|
|
69
67
|
const core = getXYRuntime();
|
|
70
68
|
const config = resolveXYConfig(cfg);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Steer message injector for CSPL hook integration
|
|
2
|
-
import {
|
|
2
|
+
import { getSessionContext } from "./tools/session-manager.js";
|
|
3
|
+
import { hasActiveTask, getCurrentTaskId } from "./task-manager.js";
|
|
3
4
|
import { handleXYMessage } from "./bot.js";
|
|
4
5
|
import { logger } from "./utils/logger.js";
|
|
5
6
|
import { randomUUID } from "node:crypto";
|
|
@@ -28,12 +29,13 @@ export async function tryInjectSteer(sessionKey, message) {
|
|
|
28
29
|
if (!sessionKey) {
|
|
29
30
|
return false;
|
|
30
31
|
}
|
|
31
|
-
const
|
|
32
|
-
if (!
|
|
32
|
+
const sessionCtx = getSessionContext(sessionKey);
|
|
33
|
+
if (!sessionCtx) {
|
|
33
34
|
return false;
|
|
34
35
|
}
|
|
35
|
-
const {
|
|
36
|
-
|
|
36
|
+
const { sessionId } = sessionCtx;
|
|
37
|
+
const activeTaskId = getCurrentTaskId(sessionId);
|
|
38
|
+
if (!hasActiveTask(sessionId)) {
|
|
37
39
|
return false;
|
|
38
40
|
}
|
|
39
41
|
if (!cachedCfg || !cachedRuntime) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { SessionContext } from "./session-manager.js";
|
|
1
2
|
/**
|
|
2
3
|
* XY calendar event tool - creates a calendar event on user's device.
|
|
3
4
|
* Requires title, dtStart (start time), and dtEnd (end time) parameters.
|
|
4
5
|
* Time format must be: yyyy-mm-dd hh:mm:ss
|
|
5
6
|
*/
|
|
6
|
-
export declare function createCalendarTool(
|
|
7
|
+
export declare function createCalendarTool(ctx: SessionContext): 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";
|
|
4
3
|
/**
|
|
5
4
|
* XY calendar event tool - creates a calendar event on user's device.
|
|
6
5
|
* Requires title, dtStart (start time), and dtEnd (end time) parameters.
|
|
7
6
|
* Time format must be: yyyy-mm-dd hh:mm:ss
|
|
8
7
|
*/
|
|
9
|
-
export function createCalendarTool(
|
|
8
|
+
export function createCalendarTool(ctx) {
|
|
9
|
+
const { config, sessionId, taskId, messageId } = ctx;
|
|
10
10
|
return {
|
|
11
11
|
name: "create_calendar_event",
|
|
12
12
|
label: "Create Calendar Event",
|
|
@@ -34,7 +34,6 @@ export function createCalendarTool(sessionKey) {
|
|
|
34
34
|
required: ["title", "dtStart", "dtEnd"],
|
|
35
35
|
},
|
|
36
36
|
async execute(toolCallId, params) {
|
|
37
|
-
const session = requireSession(sessionKey);
|
|
38
37
|
// Validate parameters
|
|
39
38
|
if (!params.title || !params.dtStart || !params.dtEnd) {
|
|
40
39
|
throw new Error("Missing required parameters: title, dtStart, and dtEnd are required");
|
|
@@ -46,7 +45,7 @@ export function createCalendarTool(sessionKey) {
|
|
|
46
45
|
throw new Error("Invalid time format. Required format: yyyy-mm-dd hh:mm:ss (e.g., 2024-01-15 14:30:00)");
|
|
47
46
|
}
|
|
48
47
|
// Get WebSocket manager
|
|
49
|
-
const wsManager = getXYWebSocketManager(
|
|
48
|
+
const wsManager = getXYWebSocketManager(config);
|
|
50
49
|
// Build CreateCalendarEvent command
|
|
51
50
|
const command = {
|
|
52
51
|
header: {
|
|
@@ -112,10 +111,10 @@ export function createCalendarTool(sessionKey) {
|
|
|
112
111
|
wsManager.on("data-event", handler);
|
|
113
112
|
// Send the command
|
|
114
113
|
sendCommand({
|
|
115
|
-
config
|
|
116
|
-
sessionId
|
|
117
|
-
taskId
|
|
118
|
-
messageId
|
|
114
|
+
config,
|
|
115
|
+
sessionId,
|
|
116
|
+
taskId,
|
|
117
|
+
messageId,
|
|
119
118
|
command,
|
|
120
119
|
})
|
|
121
120
|
.then(() => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { SessionContext } from "./session-manager.js";
|
|
1
2
|
/**
|
|
2
3
|
* call_device_tool - 通用端工具调度器。
|
|
3
4
|
* LLM 必须先通过 get_xxx_tool_schema 获取具体工具 schema,再用本工具执行。
|
|
4
5
|
*/
|
|
5
|
-
export declare function createCallDeviceTool(
|
|
6
|
+
export declare function createCallDeviceTool(ctx: SessionContext): any;
|