@ynhcj/xiaoyi-channel 0.0.1-beta → 0.0.1-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 +66 -28
- package/dist/src/channel.js +9 -2
- package/dist/src/file-download.js +10 -1
- package/dist/src/formatter.d.ts +17 -0
- package/dist/src/formatter.js +43 -0
- package/dist/src/monitor.js +38 -8
- package/dist/src/outbound.js +3 -1
- package/dist/src/push.js +2 -2
- package/dist/src/reply-dispatcher.d.ts +1 -0
- package/dist/src/reply-dispatcher.js +206 -51
- package/dist/src/task-manager.d.ts +55 -0
- package/dist/src/task-manager.js +136 -0
- package/dist/src/tools/calendar-tool.js +7 -7
- package/dist/src/tools/location-tool.js +6 -6
- package/dist/src/tools/modify-note-tool.d.ts +9 -0
- package/dist/src/tools/modify-note-tool.js +163 -0
- package/dist/src/tools/note-tool.js +4 -4
- package/dist/src/tools/search-calendar-tool.d.ts +12 -0
- package/dist/src/tools/search-calendar-tool.js +259 -0
- package/dist/src/tools/search-contact-tool.d.ts +5 -0
- package/dist/src/tools/search-contact-tool.js +168 -0
- package/dist/src/tools/search-note-tool.js +4 -4
- package/dist/src/tools/search-photo-gallery-tool.d.ts +8 -0
- package/dist/src/tools/search-photo-gallery-tool.js +184 -0
- package/dist/src/tools/search-photo-tool.d.ts +9 -0
- package/dist/src/tools/search-photo-tool.js +270 -0
- package/dist/src/tools/session-manager.js +42 -18
- package/dist/src/tools/upload-photo-tool.d.ts +9 -0
- package/dist/src/tools/upload-photo-tool.js +223 -0
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +6 -0
- package/dist/src/tools/xiaoyi-gui-tool.js +151 -0
- package/dist/src/types.d.ts +5 -1
- package/dist/src/websocket.d.ts +1 -0
- package/dist/src/websocket.js +13 -1
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -6,6 +6,7 @@ import { resolveXYConfig } from "./config.js";
|
|
|
6
6
|
import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse } from "./formatter.js";
|
|
7
7
|
import { registerSession, unregisterSession } from "./tools/session-manager.js";
|
|
8
8
|
import { configManager } from "./utils/config-manager.js";
|
|
9
|
+
import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActiveTask, } from "./task-manager.js";
|
|
9
10
|
/**
|
|
10
11
|
* Handle an incoming A2A message.
|
|
11
12
|
* This is the main entry point for message processing.
|
|
@@ -56,6 +57,22 @@ export async function handleXYMessage(params) {
|
|
|
56
57
|
}
|
|
57
58
|
// Parse the A2A message (for regular messages)
|
|
58
59
|
const parsed = parseA2AMessage(message);
|
|
60
|
+
// 🔑 检测steer模式和是否是第二条消息
|
|
61
|
+
const isSteerMode = cfg.messages?.queue?.mode === "steer";
|
|
62
|
+
const isSecondMessage = isSteerMode && hasActiveTask(parsed.sessionId);
|
|
63
|
+
if (isSecondMessage) {
|
|
64
|
+
log(`[BOT] 🔄 STEER MODE - Second message detected (will be follower)`);
|
|
65
|
+
log(`[BOT] - Session: ${parsed.sessionId}`);
|
|
66
|
+
log(`[BOT] - New taskId: ${parsed.taskId} (will replace current)`);
|
|
67
|
+
}
|
|
68
|
+
// 🔑 注册taskId(第二条消息会覆盖第一条的taskId)
|
|
69
|
+
const { isUpdate, refCount } = registerTaskId(parsed.sessionId, parsed.taskId, parsed.messageId, { incrementRef: true } // 增加引用计数
|
|
70
|
+
);
|
|
71
|
+
// 🔑 如果是第一条消息,锁定taskId防止被过早清理
|
|
72
|
+
if (!isUpdate) {
|
|
73
|
+
lockTaskId(parsed.sessionId);
|
|
74
|
+
log(`[BOT] 🔒 Locked taskId for first message`);
|
|
75
|
+
}
|
|
59
76
|
// Extract and update push_id if present
|
|
60
77
|
const pushId = extractPushId(parsed.parts);
|
|
61
78
|
if (pushId) {
|
|
@@ -83,11 +100,12 @@ export async function handleXYMessage(params) {
|
|
|
83
100
|
},
|
|
84
101
|
});
|
|
85
102
|
log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
|
|
86
|
-
//
|
|
103
|
+
// 🔑 注册session(带引用计数)
|
|
87
104
|
log(`[BOT] 📝 About to register session for tools...`);
|
|
88
105
|
log(`[BOT] - sessionKey: ${route.sessionKey}`);
|
|
89
106
|
log(`[BOT] - sessionId: ${parsed.sessionId}`);
|
|
90
107
|
log(`[BOT] - taskId: ${parsed.taskId}`);
|
|
108
|
+
log(`[BOT] - isSecondMessage: ${isSecondMessage}`);
|
|
91
109
|
registerSession(route.sessionKey, {
|
|
92
110
|
config,
|
|
93
111
|
sessionId: parsed.sessionId,
|
|
@@ -96,6 +114,18 @@ export async function handleXYMessage(params) {
|
|
|
96
114
|
agentId: route.accountId,
|
|
97
115
|
});
|
|
98
116
|
log(`[BOT] ✅ Session registered for tools`);
|
|
117
|
+
// 🔑 发送初始状态更新(第二条消息也要发,用新taskId)
|
|
118
|
+
log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
|
|
119
|
+
void sendStatusUpdate({
|
|
120
|
+
config,
|
|
121
|
+
sessionId: parsed.sessionId,
|
|
122
|
+
taskId: parsed.taskId,
|
|
123
|
+
messageId: parsed.messageId,
|
|
124
|
+
text: isSecondMessage ? "新消息已接收,正在处理..." : "任务正在处理中,请稍后~",
|
|
125
|
+
state: "working",
|
|
126
|
+
}).catch((err) => {
|
|
127
|
+
error(`Failed to send initial status update:`, err);
|
|
128
|
+
});
|
|
99
129
|
// Extract text and files from parts
|
|
100
130
|
const text = extractTextFromParts(parsed.parts);
|
|
101
131
|
const fileParts = extractFileParts(parsed.parts);
|
|
@@ -143,32 +173,30 @@ export async function handleXYMessage(params) {
|
|
|
143
173
|
ReplyToBody: undefined, // A2A protocol doesn't support reply/quote
|
|
144
174
|
...mediaPayload,
|
|
145
175
|
});
|
|
146
|
-
//
|
|
147
|
-
log(`[
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
taskId: parsed.taskId,
|
|
152
|
-
messageId: parsed.messageId,
|
|
153
|
-
text: "任务正在处理中,请稍后~",
|
|
154
|
-
state: "working",
|
|
155
|
-
}).catch((err) => {
|
|
156
|
-
error(`Failed to send initial status update:`, err);
|
|
157
|
-
});
|
|
158
|
-
// Create reply dispatcher (following feishu pattern)
|
|
159
|
-
log(`[BOT-DISPATCHER] 🎯 Creating reply dispatcher for session=${parsed.sessionId}, taskId=${parsed.taskId}, messageId=${parsed.messageId}`);
|
|
176
|
+
// 🔑 创建dispatcher(dispatcher会自动使用动态taskId)
|
|
177
|
+
log(`[BOT-DISPATCHER] 🎯 Creating reply dispatcher`);
|
|
178
|
+
log(`[BOT-DISPATCHER] - session: ${parsed.sessionId}`);
|
|
179
|
+
log(`[BOT-DISPATCHER] - taskId: ${parsed.taskId}`);
|
|
180
|
+
log(`[BOT-DISPATCHER] - isSecondMessage: ${isSecondMessage}`);
|
|
160
181
|
const { dispatcher, replyOptions, markDispatchIdle, startStatusInterval } = createXYReplyDispatcher({
|
|
161
182
|
cfg,
|
|
162
183
|
runtime,
|
|
163
184
|
sessionId: parsed.sessionId,
|
|
164
185
|
taskId: parsed.taskId,
|
|
165
186
|
messageId: parsed.messageId,
|
|
166
|
-
accountId: route.accountId,
|
|
187
|
+
accountId: route.accountId,
|
|
188
|
+
isSteerFollower: isSecondMessage, // 🔑 标记第二条消息
|
|
167
189
|
});
|
|
168
190
|
log(`[BOT-DISPATCHER] ✅ Reply dispatcher created successfully`);
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
|
|
191
|
+
// 🔑 只有第一条消息启动状态定时器
|
|
192
|
+
// 第二条消息会很快返回,不需要定时器
|
|
193
|
+
if (!isSecondMessage) {
|
|
194
|
+
startStatusInterval();
|
|
195
|
+
log(`[BOT-DISPATCHER] ✅ Status interval started for first message`);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
log(`[BOT-DISPATCHER] ⏭️ Skipped status interval for steer follower`);
|
|
199
|
+
}
|
|
172
200
|
log(`xy: dispatching to agent (session=${parsed.sessionId})`);
|
|
173
201
|
// Dispatch to OpenClaw core using correct API (following feishu pattern)
|
|
174
202
|
log(`[BOT] 🚀 Starting dispatcher with session: ${route.sessionKey}`);
|
|
@@ -176,11 +204,18 @@ export async function handleXYMessage(params) {
|
|
|
176
204
|
dispatcher,
|
|
177
205
|
onSettled: () => {
|
|
178
206
|
log(`[BOT] 🏁 onSettled called for session: ${route.sessionKey}`);
|
|
179
|
-
log(`[BOT] -
|
|
207
|
+
log(`[BOT] - isSecondMessage: ${isSecondMessage}`);
|
|
180
208
|
markDispatchIdle();
|
|
181
|
-
//
|
|
209
|
+
// 🔑 减少引用计数
|
|
210
|
+
decrementTaskIdRef(parsed.sessionId);
|
|
211
|
+
// 🔑 如果是第一条消息完成,解锁
|
|
212
|
+
if (!isSecondMessage) {
|
|
213
|
+
unlockTaskId(parsed.sessionId);
|
|
214
|
+
log(`[BOT] 🔓 Unlocked taskId (first message completed)`);
|
|
215
|
+
}
|
|
216
|
+
// 减少session引用计数
|
|
182
217
|
unregisterSession(route.sessionKey);
|
|
183
|
-
log(`[BOT] ✅
|
|
218
|
+
log(`[BOT] ✅ Cleanup completed`);
|
|
184
219
|
},
|
|
185
220
|
run: () => core.channel.reply.dispatchReplyFromConfig({
|
|
186
221
|
ctx: ctxPayload,
|
|
@@ -197,25 +232,28 @@ export async function handleXYMessage(params) {
|
|
|
197
232
|
error("Failed to handle XY message:", err);
|
|
198
233
|
runtime.error?.(`xy: Failed to handle message: ${String(err)}`);
|
|
199
234
|
log(`[BOT] ❌ Error occurred, attempting cleanup...`);
|
|
200
|
-
//
|
|
235
|
+
// 🔑 错误时也要清理taskId和session
|
|
201
236
|
try {
|
|
202
|
-
const core = getXYRuntime();
|
|
203
237
|
const params = message.params;
|
|
204
238
|
const sessionId = params?.sessionId;
|
|
205
239
|
if (sessionId) {
|
|
206
|
-
log(`[BOT] 🧹 Cleaning up
|
|
240
|
+
log(`[BOT] 🧹 Cleaning up after error: ${sessionId}`);
|
|
241
|
+
// 清理 taskId
|
|
242
|
+
decrementTaskIdRef(sessionId);
|
|
243
|
+
unlockTaskId(sessionId);
|
|
244
|
+
// 清理 session
|
|
245
|
+
const core = getXYRuntime();
|
|
207
246
|
const route = core.channel.routing.resolveAgentRoute({
|
|
208
247
|
cfg,
|
|
209
248
|
channel: "xiaoyi-channel",
|
|
210
249
|
accountId,
|
|
211
250
|
peer: {
|
|
212
251
|
kind: "direct",
|
|
213
|
-
id: sessionId,
|
|
252
|
+
id: sessionId,
|
|
214
253
|
},
|
|
215
254
|
});
|
|
216
|
-
log(`[BOT] - Unregistering session: ${route.sessionKey}`);
|
|
217
255
|
unregisterSession(route.sessionKey);
|
|
218
|
-
log(`[BOT] ✅
|
|
256
|
+
log(`[BOT] ✅ Cleanup completed after error`);
|
|
219
257
|
}
|
|
220
258
|
}
|
|
221
259
|
catch (cleanupErr) {
|
package/dist/src/channel.js
CHANGED
|
@@ -5,7 +5,13 @@ import { xyOnboardingAdapter } from "./onboarding.js";
|
|
|
5
5
|
import { locationTool } from "./tools/location-tool.js";
|
|
6
6
|
import { noteTool } from "./tools/note-tool.js";
|
|
7
7
|
import { searchNoteTool } from "./tools/search-note-tool.js";
|
|
8
|
+
import { modifyNoteTool } from "./tools/modify-note-tool.js";
|
|
8
9
|
import { calendarTool } from "./tools/calendar-tool.js";
|
|
10
|
+
import { searchCalendarTool } from "./tools/search-calendar-tool.js";
|
|
11
|
+
// import { searchContactTool } from "./tools/search-contact-tool.js"; // 暂时禁用
|
|
12
|
+
import { searchPhotoGalleryTool } from "./tools/search-photo-gallery-tool.js";
|
|
13
|
+
import { uploadPhotoTool } from "./tools/upload-photo-tool.js";
|
|
14
|
+
import { xiaoyiGuiTool } from "./tools/xiaoyi-gui-tool.js";
|
|
9
15
|
/**
|
|
10
16
|
* Xiaoyi Channel Plugin for OpenClaw.
|
|
11
17
|
* Implements Xiaoyi A2A protocol with dual WebSocket connections.
|
|
@@ -23,7 +29,7 @@ export const xyPlugin = {
|
|
|
23
29
|
agentPrompt: {
|
|
24
30
|
messageToolHints: () => [
|
|
25
31
|
"- xiaoyi targeting: omit `target` to reply to the current conversation (auto-inferred). Explicit targets: `default`",
|
|
26
|
-
"- sendMedia requires a text reply"
|
|
32
|
+
"- If the user requests a file, you can call the message tool with the xiaoyi-channel channel to return it. Note: sendMedia requires a text reply."
|
|
27
33
|
],
|
|
28
34
|
},
|
|
29
35
|
capabilities: {
|
|
@@ -45,7 +51,7 @@ export const xyPlugin = {
|
|
|
45
51
|
},
|
|
46
52
|
outbound: xyOutbound,
|
|
47
53
|
onboarding: xyOnboardingAdapter,
|
|
48
|
-
agentTools: [locationTool, noteTool, searchNoteTool, calendarTool],
|
|
54
|
+
agentTools: [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool], // searchContactTool 已暂时禁用
|
|
49
55
|
messaging: {
|
|
50
56
|
normalizeTarget: (raw) => {
|
|
51
57
|
const trimmed = raw.trim();
|
|
@@ -81,6 +87,7 @@ export const xyPlugin = {
|
|
|
81
87
|
runtime: context.runtime,
|
|
82
88
|
abortSignal: context.abortSignal,
|
|
83
89
|
accountId: context.accountId,
|
|
90
|
+
setStatus: context.setStatus,
|
|
84
91
|
});
|
|
85
92
|
},
|
|
86
93
|
},
|
|
@@ -8,8 +8,10 @@ import { logger } from "./utils/logger.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export async function downloadFile(url, destPath) {
|
|
10
10
|
logger.debug(`Downloading file from ${url} to ${destPath}`);
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
const timeout = setTimeout(() => controller.abort(), 30000); // 30 seconds timeout
|
|
11
13
|
try {
|
|
12
|
-
const response = await fetch(url);
|
|
14
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
13
15
|
if (!response.ok) {
|
|
14
16
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
15
17
|
}
|
|
@@ -19,9 +21,16 @@ export async function downloadFile(url, destPath) {
|
|
|
19
21
|
logger.debug(`File downloaded successfully: ${destPath}`);
|
|
20
22
|
}
|
|
21
23
|
catch (error) {
|
|
24
|
+
if (error.name === 'AbortError') {
|
|
25
|
+
logger.error(`Download timeout (30s) for ${url}`);
|
|
26
|
+
throw new Error(`Download timeout after 30 seconds`);
|
|
27
|
+
}
|
|
22
28
|
logger.error(`Failed to download file from ${url}:`, error);
|
|
23
29
|
throw error;
|
|
24
30
|
}
|
|
31
|
+
finally {
|
|
32
|
+
clearTimeout(timeout);
|
|
33
|
+
}
|
|
25
34
|
}
|
|
26
35
|
/**
|
|
27
36
|
* Download files from A2A file parts.
|
package/dist/src/formatter.d.ts
CHANGED
|
@@ -20,6 +20,23 @@ export interface SendA2AResponseParams {
|
|
|
20
20
|
* Send an A2A artifact update response.
|
|
21
21
|
*/
|
|
22
22
|
export declare function sendA2AResponse(params: SendA2AResponseParams): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Parameters for sending a reasoning text update (intermediate, streamed).
|
|
25
|
+
*/
|
|
26
|
+
export interface SendReasoningTextUpdateParams {
|
|
27
|
+
config: XYChannelConfig;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
taskId: string;
|
|
30
|
+
messageId: string;
|
|
31
|
+
text: string;
|
|
32
|
+
append?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Send an A2A artifact-update with reasoningText part.
|
|
36
|
+
* Used for onToolStart, onToolResult, onReasoningStream, onReasoningEnd, onPartialReply.
|
|
37
|
+
* append=true, final=false, lastChunk=true, text is suffixed with newline for markdown rendering.
|
|
38
|
+
*/
|
|
39
|
+
export declare function sendReasoningTextUpdate(params: SendReasoningTextUpdateParams): Promise<void>;
|
|
23
40
|
/**
|
|
24
41
|
* Parameters for sending a status update.
|
|
25
42
|
*/
|
package/dist/src/formatter.js
CHANGED
|
@@ -67,6 +67,49 @@ export async function sendA2AResponse(params) {
|
|
|
67
67
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
68
68
|
log(`[A2A_RESPONSE] ✅ Message sent successfully`);
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Send an A2A artifact-update with reasoningText part.
|
|
72
|
+
* Used for onToolStart, onToolResult, onReasoningStream, onReasoningEnd, onPartialReply.
|
|
73
|
+
* append=true, final=false, lastChunk=true, text is suffixed with newline for markdown rendering.
|
|
74
|
+
*/
|
|
75
|
+
export async function sendReasoningTextUpdate(params) {
|
|
76
|
+
const { config, sessionId, taskId, messageId, text, append = true } = params;
|
|
77
|
+
const runtime = getXYRuntime();
|
|
78
|
+
const log = runtime?.log ?? console.log;
|
|
79
|
+
const error = runtime?.error ?? console.error;
|
|
80
|
+
const artifact = {
|
|
81
|
+
taskId,
|
|
82
|
+
kind: "artifact-update",
|
|
83
|
+
append,
|
|
84
|
+
lastChunk: true,
|
|
85
|
+
final: false,
|
|
86
|
+
artifact: {
|
|
87
|
+
artifactId: uuidv4(),
|
|
88
|
+
parts: [
|
|
89
|
+
{
|
|
90
|
+
kind: "reasoningText",
|
|
91
|
+
reasoningText: text,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
const jsonRpcResponse = {
|
|
97
|
+
jsonrpc: "2.0",
|
|
98
|
+
id: messageId,
|
|
99
|
+
result: artifact,
|
|
100
|
+
};
|
|
101
|
+
const wsManager = getXYWebSocketManager(config);
|
|
102
|
+
const outboundMessage = {
|
|
103
|
+
msgType: "agent_response",
|
|
104
|
+
agentId: config.agentId,
|
|
105
|
+
sessionId,
|
|
106
|
+
taskId,
|
|
107
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
108
|
+
};
|
|
109
|
+
log(`[REASONING_TEXT] 📤 Sending reasoningText update: sessionId=${sessionId}, taskId=${taskId}, text.length=${text.length}`);
|
|
110
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
111
|
+
log(`[REASONING_TEXT] ✅ Sent successfully`);
|
|
112
|
+
}
|
|
70
113
|
/**
|
|
71
114
|
* Send an A2A task status update.
|
|
72
115
|
* Follows A2A protocol standard format with nested status object.
|
package/dist/src/monitor.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { resolveXYConfig } from "./config.js";
|
|
2
2
|
import { getXYWebSocketManager, diagnoseAllManagers, cleanupOrphanConnections, removeXYWebSocketManager } from "./client.js";
|
|
3
3
|
import { handleXYMessage } from "./bot.js";
|
|
4
|
+
import { parseA2AMessage } from "./parser.js";
|
|
5
|
+
import { hasActiveTask } from "./task-manager.js";
|
|
4
6
|
/**
|
|
5
7
|
* Per-session serial queue that ensures messages from the same session are processed
|
|
6
8
|
* in arrival order while allowing different sessions to run concurrently.
|
|
@@ -94,11 +96,39 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
94
96
|
log(`[MONITOR-HANDLER] 🧹 Cleaned up messageKey=${messageKey}, remaining active: ${activeMessages.size}`);
|
|
95
97
|
}
|
|
96
98
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
// 🔑 核心改造:检测steer模式
|
|
100
|
+
// 需要提前解析消息以获取sessionId
|
|
101
|
+
try {
|
|
102
|
+
const parsed = parseA2AMessage(message);
|
|
103
|
+
const steerMode = cfg.messages?.queue?.mode === "steer";
|
|
104
|
+
const hasActiveRun = hasActiveTask(parsed.sessionId);
|
|
105
|
+
if (steerMode && hasActiveRun) {
|
|
106
|
+
// Steer模式且有活跃任务:不入队列,直接并发执行
|
|
107
|
+
log(`[MONITOR-HANDLER] 🔄 STEER MODE: Executing concurrently for messageKey=${messageKey}`);
|
|
108
|
+
log(`[MONITOR-HANDLER] - sessionId: ${parsed.sessionId}`);
|
|
109
|
+
log(`[MONITOR-HANDLER] - Bypassing queue to allow message insertion`);
|
|
110
|
+
void task().catch((err) => {
|
|
111
|
+
error(`XY gateway: concurrent steer task failed for ${messageKey}: ${String(err)}`);
|
|
112
|
+
activeMessages.delete(messageKey);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// 正常模式:入队列串行执行
|
|
117
|
+
log(`[MONITOR-HANDLER] 📋 NORMAL MODE: Enqueuing for messageKey=${messageKey}`);
|
|
118
|
+
void enqueue(sessionId, task).catch((err) => {
|
|
119
|
+
error(`XY gateway: queue processing failed for session ${sessionId}: ${String(err)}`);
|
|
120
|
+
activeMessages.delete(messageKey);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (parseErr) {
|
|
125
|
+
// 解析失败,回退到正常队列模式
|
|
126
|
+
error(`[MONITOR-HANDLER] Failed to parse message for steer detection: ${String(parseErr)}`);
|
|
127
|
+
void enqueue(sessionId, task).catch((err) => {
|
|
128
|
+
error(`XY gateway: queue processing failed for session ${sessionId}: ${String(err)}`);
|
|
129
|
+
activeMessages.delete(messageKey);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
102
132
|
};
|
|
103
133
|
const connectedHandler = (serverId) => {
|
|
104
134
|
if (!loggedServers.has(serverId)) {
|
|
@@ -150,12 +180,12 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
150
180
|
};
|
|
151
181
|
const handleAbort = () => {
|
|
152
182
|
log("XY gateway: abort signal received, stopping");
|
|
153
|
-
|
|
154
|
-
|
|
183
|
+
cleanup();
|
|
184
|
+
log("XY gateway stopped");
|
|
155
185
|
resolve();
|
|
156
186
|
};
|
|
157
187
|
if (opts.abortSignal?.aborted) {
|
|
158
|
-
|
|
188
|
+
cleanup();
|
|
159
189
|
resolve();
|
|
160
190
|
return;
|
|
161
191
|
}
|
package/dist/src/outbound.js
CHANGED
|
@@ -109,8 +109,10 @@ export const xyOutbound = {
|
|
|
109
109
|
const pushService = new XYPushService(config);
|
|
110
110
|
// Extract title (first 57 chars or first line)
|
|
111
111
|
const title = text.split("\n")[0].slice(0, 57);
|
|
112
|
+
// Truncate push content to max length 1000
|
|
113
|
+
const pushText = text.length > 1000 ? text.slice(0, 1000) : text;
|
|
112
114
|
// Send push message (content, title, data, sessionId)
|
|
113
|
-
await pushService.sendPush(
|
|
115
|
+
await pushService.sendPush(pushText, title, undefined, actualTo);
|
|
114
116
|
console.log(`[xyOutbound.sendText] Completed successfully`);
|
|
115
117
|
// Return message info
|
|
116
118
|
return {
|
package/dist/src/push.js
CHANGED