@ynhcj/xiaoyi-channel 1.1.26 → 1.1.28
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/index.js +26 -69
- package/dist/src/approval-bridge.d.ts +48 -0
- package/dist/src/approval-bridge.js +382 -0
- package/dist/src/bot.js +132 -73
- package/dist/src/channel.js +59 -5
- package/dist/src/client.js +13 -23
- package/dist/src/cron-command.d.ts +15 -0
- package/dist/src/cron-command.js +49 -0
- package/dist/src/cron-query-handler.d.ts +7 -0
- package/dist/src/cron-query-handler.js +189 -0
- package/dist/src/cspl/call_api.d.ts +2 -0
- package/dist/src/cspl/call_api.js +107 -0
- package/dist/src/cspl/config.d.ts +4 -17
- package/dist/src/cspl/config.js +100 -70
- package/dist/src/cspl/configs.json +10 -0
- package/dist/src/cspl/constants.d.ts +49 -24
- package/dist/src/cspl/constants.js +46 -16
- package/dist/src/cspl/sentinel_hook.d.ts +2 -0
- package/dist/src/cspl/sentinel_hook.js +103 -0
- package/dist/src/cspl/steer-context.js +1 -1
- package/dist/src/cspl/upload_file.d.ts +1 -0
- package/dist/src/cspl/upload_file.js +211 -0
- package/dist/src/cspl/utils.d.ts +17 -2
- package/dist/src/cspl/utils.js +271 -15
- package/dist/src/file-upload.d.ts +5 -0
- package/dist/src/file-upload.js +102 -0
- package/dist/src/formatter.d.ts +43 -1
- package/dist/src/formatter.js +171 -41
- package/dist/src/monitor.js +64 -43
- package/dist/src/outbound.js +8 -9
- package/dist/src/parser.d.ts +8 -1
- package/dist/src/parser.js +71 -0
- package/dist/src/provider.js +51 -17
- package/dist/src/push.d.ts +11 -1
- package/dist/src/push.js +101 -17
- package/dist/src/reply-dispatcher.js +152 -59
- package/dist/src/self-evolution-handler.d.ts +1 -1
- package/dist/src/self-evolution-handler.js +14 -3
- package/dist/src/sensitive-redactor.d.ts +4 -0
- package/dist/src/sensitive-redactor.js +364 -0
- package/dist/src/task-manager.js +6 -10
- package/dist/src/tools/agent-as-skill-tool.d.ts +7 -0
- package/dist/src/tools/agent-as-skill-tool.js +190 -0
- package/dist/src/tools/calendar-tool.js +3 -2
- package/dist/src/tools/call-phone-tool.js +3 -2
- package/dist/src/tools/check-plugin-privilege-tool.d.ts +6 -0
- package/dist/src/tools/check-plugin-privilege-tool.js +182 -0
- package/dist/src/tools/create-alarm-tool.js +3 -2
- package/dist/src/tools/create-all-tools.js +11 -3
- package/dist/src/tools/delete-alarm-tool.js +3 -2
- package/dist/src/tools/device-tool-map.d.ts +1 -1
- package/dist/src/tools/device-tool-map.js +12 -5
- package/dist/src/tools/discover-cross-devices-tool.d.ts +2 -0
- package/dist/src/tools/discover-cross-devices-tool.js +235 -0
- package/dist/src/tools/display-a2ui-card-tool.d.ts +2 -0
- package/dist/src/tools/display-a2ui-card-tool.js +85 -0
- package/dist/src/tools/find-pc-devices-tool.d.ts +2 -1
- package/dist/src/tools/find-pc-devices-tool.js +85 -88
- package/dist/src/tools/get-collection-tool-schema.js +1 -1
- package/dist/src/tools/location-tool.js +3 -2
- package/dist/src/tools/modify-alarm-tool.js +3 -2
- package/dist/src/tools/modify-note-tool.js +3 -2
- package/dist/src/tools/note-tool.js +3 -2
- package/dist/src/tools/query-app-message-tool.js +4 -3
- package/dist/src/tools/query-memory-data-tool.js +4 -3
- package/dist/src/tools/query-todo-task-tool.js +4 -3
- package/dist/src/tools/save-file-to-phone-tool.js +3 -2
- package/dist/src/tools/save-media-to-gallery-tool.js +3 -2
- package/dist/src/tools/schema-tool-factory.js +1 -1
- package/dist/src/tools/search-alarm-tool.js +3 -2
- package/dist/src/tools/search-calendar-tool.js +3 -2
- package/dist/src/tools/search-contact-tool.js +3 -2
- package/dist/src/tools/search-email-tool.js +4 -3
- package/dist/src/tools/search-file-tool.js +8 -9
- package/dist/src/tools/search-message-tool.js +2 -1
- package/dist/src/tools/search-note-tool.js +3 -2
- package/dist/src/tools/search-photo-gallery-tool.js +5 -4
- package/dist/src/tools/send-cross-device-task-tool.d.ts +2 -0
- package/dist/src/tools/send-cross-device-task-tool.js +299 -0
- package/dist/src/tools/send-email-tool.js +4 -3
- package/dist/src/tools/send-file-to-user-tool.d.ts +1 -1
- package/dist/src/tools/send-file-to-user-tool.js +37 -8
- package/dist/src/tools/send-html-card-tool.d.ts +7 -0
- package/dist/src/tools/send-html-card-tool.js +113 -0
- package/dist/src/tools/send-message-tool.js +2 -1
- package/dist/src/tools/session-manager.d.ts +17 -1
- package/dist/src/tools/session-manager.js +87 -1
- package/dist/src/tools/upload-file-tool.js +9 -7
- package/dist/src/tools/upload-photo-tool.js +5 -4
- package/dist/src/tools/xiaoyi-add-collection-tool.js +5 -3
- package/dist/src/tools/xiaoyi-collection-tool.js +4 -3
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +4 -3
- package/dist/src/tools/xiaoyi-gui-tool.js +8 -2
- package/dist/src/trigger-handler.js +4 -7
- package/dist/src/types.d.ts +25 -1
- package/dist/src/utils/config-manager.js +3 -6
- package/dist/src/utils/logger.d.ts +8 -0
- package/dist/src/utils/logger.js +69 -34
- package/dist/src/utils/pushdata-manager.js +1 -5
- package/dist/src/utils/pushid-manager.js +1 -2
- package/dist/src/utils/runtime-manager.js +1 -4
- package/dist/src/websocket.d.ts +3 -0
- package/dist/src/websocket.js +242 -38
- package/package.json +1 -1
package/dist/src/formatter.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface SendA2AResponseParams {
|
|
|
17
17
|
}>;
|
|
18
18
|
errorCode?: number | string;
|
|
19
19
|
errorMessage?: string;
|
|
20
|
+
log?: boolean;
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
22
23
|
* Send an A2A artifact update response.
|
|
@@ -63,12 +64,53 @@ export interface SendCommandParams {
|
|
|
63
64
|
sessionId: string;
|
|
64
65
|
taskId: string;
|
|
65
66
|
messageId: string;
|
|
66
|
-
command
|
|
67
|
+
command?: A2ACommand;
|
|
68
|
+
commands?: A2ACommand[];
|
|
69
|
+
/** toolCallId from the tool's execute() — used for cron detection via hook-set Map. */
|
|
70
|
+
toolCallId?: string;
|
|
71
|
+
/** When true, the artifact-update is sent with final=true. Default: false. */
|
|
72
|
+
final?: boolean;
|
|
67
73
|
}
|
|
68
74
|
/**
|
|
69
75
|
* Send a command as an artifact update (final=false).
|
|
76
|
+
*
|
|
77
|
+
* Cron-aware: if the sessionId starts with the cron prefix ("cron-"),
|
|
78
|
+
* the command is delivered through the push channel instead of the
|
|
79
|
+
* WebSocket session, because cron-triggered tool calls have no active
|
|
80
|
+
* WebSocket session. The device receives the push, executes the command,
|
|
81
|
+
* and returns results through the normal WebSocket path — so response
|
|
82
|
+
* listening in the calling tool works unchanged.
|
|
70
83
|
*/
|
|
71
84
|
export declare function sendCommand(params: SendCommandParams): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Parameters for sending a card (e.g., HTML H5 card).
|
|
87
|
+
*/
|
|
88
|
+
export interface SendCardParams {
|
|
89
|
+
config: XYChannelConfig;
|
|
90
|
+
sessionId: string;
|
|
91
|
+
taskId: string;
|
|
92
|
+
messageId: string;
|
|
93
|
+
/** toolCallId from the tool's execute() — used for cron detection via hook-set Map. */
|
|
94
|
+
toolCallId?: string;
|
|
95
|
+
/** When true, the artifact-update is sent with final=true. Default: false. */
|
|
96
|
+
final?: boolean;
|
|
97
|
+
/** Array of card data objects to send. */
|
|
98
|
+
cardsInfo: CardDataObject[];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Card data object for sending display cards.
|
|
102
|
+
*/
|
|
103
|
+
export interface CardDataObject {
|
|
104
|
+
cardName: string;
|
|
105
|
+
cardData: Record<string, any>;
|
|
106
|
+
displayType: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Send a card (e.g., HTML H5 card) as an artifact update (final=false).
|
|
110
|
+
*
|
|
111
|
+
* Cron-aware: same routing logic as sendCommand.
|
|
112
|
+
*/
|
|
113
|
+
export declare function sendCard(params: SendCardParams): Promise<void>;
|
|
72
114
|
/**
|
|
73
115
|
* Parameters for sending a clearContext response.
|
|
74
116
|
*/
|
package/dist/src/formatter.js
CHANGED
|
@@ -3,11 +3,49 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
3
3
|
import { getXYWebSocketManager } from "./client.js";
|
|
4
4
|
import { logger } from "./utils/logger.js";
|
|
5
5
|
import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
6
|
+
import { redactSensitiveText, containsSensitiveInfo } from "./sensitive-redactor.js";
|
|
7
|
+
import { rewriteOutboundApprovalText } from "./approval-bridge.js";
|
|
8
|
+
import { isCronToolCall } from "./tools/session-manager.js";
|
|
9
|
+
// ─────────────────────────────────────────────────────────────
|
|
10
|
+
// 敏感信息脱敏辅助函数
|
|
11
|
+
// ─────────────────────────────────────────────────────────────
|
|
12
|
+
const MESSAGE_CONTENT_KEYS = new Set(["text", "reasoningText", "content", "message"]);
|
|
13
|
+
function redactMessagePayload(value, currentKey) {
|
|
14
|
+
if (value === null || value === undefined) {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === "string") {
|
|
18
|
+
if (currentKey === undefined || MESSAGE_CONTENT_KEYS.has(currentKey)) {
|
|
19
|
+
return redactSensitiveText(value);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
return value.map(item => redactMessagePayload(item, currentKey));
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === "object") {
|
|
27
|
+
const result = {};
|
|
28
|
+
for (const key of Object.keys(value)) {
|
|
29
|
+
result[key] = redactMessagePayload(value[key], key);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function buildTextPreview(text) {
|
|
36
|
+
if (typeof text !== "string" || text.length === 0) {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
return text.length <= 10 ? text : `${text.slice(0, 5)}***${text.slice(-5)}`;
|
|
40
|
+
}
|
|
6
41
|
/**
|
|
7
42
|
* Send an A2A artifact update response.
|
|
8
43
|
*/
|
|
9
44
|
export async function sendA2AResponse(params) {
|
|
10
|
-
const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage } = params;
|
|
45
|
+
const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage, log: shouldLog = true } = params;
|
|
46
|
+
const log = logger.withContext(sessionId, taskId);
|
|
47
|
+
// 审批桥接:将 OpenClaw 的审批提示翻译成用户友好的确认文案
|
|
48
|
+
const bridgedText = text === undefined ? text : rewriteOutboundApprovalText(sessionId, text);
|
|
11
49
|
// Build artifact update event
|
|
12
50
|
const artifact = {
|
|
13
51
|
taskId,
|
|
@@ -21,10 +59,10 @@ export async function sendA2AResponse(params) {
|
|
|
21
59
|
},
|
|
22
60
|
};
|
|
23
61
|
// Add text part (even if empty string, to maintain parts structure)
|
|
24
|
-
if (
|
|
62
|
+
if (bridgedText !== undefined) {
|
|
25
63
|
artifact.artifact.parts.push({
|
|
26
64
|
kind: "text",
|
|
27
|
-
text,
|
|
65
|
+
text: bridgedText,
|
|
28
66
|
});
|
|
29
67
|
}
|
|
30
68
|
// Add file parts if provided
|
|
@@ -34,6 +72,8 @@ export async function sendA2AResponse(params) {
|
|
|
34
72
|
data: { fileInfo: files },
|
|
35
73
|
});
|
|
36
74
|
}
|
|
75
|
+
// 对消息内容字段做敏感信息脱敏,不修改协议层的 id 等字段
|
|
76
|
+
artifact.artifact.parts = redactMessagePayload(artifact.artifact.parts, "parts");
|
|
37
77
|
// Build JSON-RPC response
|
|
38
78
|
const jsonRpcResponse = {
|
|
39
79
|
jsonrpc: "2.0",
|
|
@@ -46,7 +86,7 @@ export async function sendA2AResponse(params) {
|
|
|
46
86
|
code: errorCode,
|
|
47
87
|
message: errorMessage ?? "任务执行异常,请重试",
|
|
48
88
|
};
|
|
49
|
-
|
|
89
|
+
log.log(`[A2A_RESPONSE] Including error code: ${errorCode}`);
|
|
50
90
|
}
|
|
51
91
|
// Send via WebSocket
|
|
52
92
|
const wsManager = getXYWebSocketManager(config);
|
|
@@ -57,14 +97,14 @@ export async function sendA2AResponse(params) {
|
|
|
57
97
|
taskId,
|
|
58
98
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
59
99
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
logger.log(`[A2A_RESPONSE] - text: ${text.length <= 10 ? text : text.slice(0, 5) + '***' + text.slice(-5)}`);
|
|
65
|
-
logger.log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
|
|
100
|
+
if (shouldLog) {
|
|
101
|
+
const redactedText = redactSensitiveText(bridgedText ?? "");
|
|
102
|
+
log.log(`[A2A_RESPONSE] Sending artifact-update, append=${append}, final=${final}, text=${buildTextPreview(redactedText)}, files=${files?.length ?? 0}, sensitive=${containsSensitiveInfo(bridgedText ?? "")}`);
|
|
103
|
+
}
|
|
66
104
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
67
|
-
|
|
105
|
+
if (shouldLog) {
|
|
106
|
+
log.log(`[A2A_RESPONSE] Message sent successfully`);
|
|
107
|
+
}
|
|
68
108
|
}
|
|
69
109
|
/**
|
|
70
110
|
* Send an A2A artifact-update with reasoningText part.
|
|
@@ -73,6 +113,9 @@ export async function sendA2AResponse(params) {
|
|
|
73
113
|
*/
|
|
74
114
|
export async function sendReasoningTextUpdate(params) {
|
|
75
115
|
const { config, sessionId, taskId, messageId, text, append = true } = params;
|
|
116
|
+
const log = logger.withContext(sessionId, taskId);
|
|
117
|
+
// 审批桥接
|
|
118
|
+
const bridgedText = rewriteOutboundApprovalText(sessionId, text);
|
|
76
119
|
const artifact = {
|
|
77
120
|
taskId,
|
|
78
121
|
kind: "artifact-update",
|
|
@@ -84,11 +127,13 @@ export async function sendReasoningTextUpdate(params) {
|
|
|
84
127
|
parts: [
|
|
85
128
|
{
|
|
86
129
|
kind: "reasoningText",
|
|
87
|
-
reasoningText:
|
|
130
|
+
reasoningText: bridgedText,
|
|
88
131
|
},
|
|
89
132
|
],
|
|
90
133
|
},
|
|
91
134
|
};
|
|
135
|
+
// 对消息内容字段做敏感信息脱敏
|
|
136
|
+
artifact.artifact.parts = redactMessagePayload(artifact.artifact.parts, "parts");
|
|
92
137
|
const jsonRpcResponse = {
|
|
93
138
|
jsonrpc: "2.0",
|
|
94
139
|
id: messageId,
|
|
@@ -114,21 +159,26 @@ export async function sendStatusUpdate(params) {
|
|
|
114
159
|
// fall back to closure-captured values
|
|
115
160
|
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
116
161
|
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
162
|
+
const log = logger.withContext(sessionId, currentTaskId);
|
|
163
|
+
// 审批桥接和脱敏
|
|
164
|
+
const bridgedText = rewriteOutboundApprovalText(sessionId, text);
|
|
165
|
+
const redactedText = redactSensitiveText(bridgedText);
|
|
117
166
|
// Build status update event following A2A protocol standard
|
|
167
|
+
const statusMessage = redactMessagePayload({
|
|
168
|
+
role: "agent",
|
|
169
|
+
parts: [
|
|
170
|
+
{
|
|
171
|
+
kind: "text",
|
|
172
|
+
text: bridgedText,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
});
|
|
118
176
|
const statusUpdate = {
|
|
119
177
|
taskId: currentTaskId,
|
|
120
178
|
kind: "status-update",
|
|
121
179
|
final: false, // Status updates should not end the stream
|
|
122
180
|
status: {
|
|
123
|
-
message:
|
|
124
|
-
role: "agent",
|
|
125
|
-
parts: [
|
|
126
|
-
{
|
|
127
|
-
kind: "text",
|
|
128
|
-
text,
|
|
129
|
-
},
|
|
130
|
-
],
|
|
131
|
-
},
|
|
181
|
+
message: statusMessage,
|
|
132
182
|
state,
|
|
133
183
|
},
|
|
134
184
|
};
|
|
@@ -147,21 +197,38 @@ export async function sendStatusUpdate(params) {
|
|
|
147
197
|
taskId: currentTaskId,
|
|
148
198
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
149
199
|
};
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
logger.log(`[A2A_STATUS] - taskId: ${currentTaskId}`);
|
|
153
|
-
logger.log(`[A2A_STATUS] - text: "${text}"`);
|
|
200
|
+
// Log complete response body
|
|
201
|
+
log.log(`[A2A_STATUS] Sending status-update, text="${redactedText}"`);
|
|
154
202
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
155
203
|
}
|
|
156
204
|
/**
|
|
157
205
|
* Send a command as an artifact update (final=false).
|
|
206
|
+
*
|
|
207
|
+
* Cron-aware: if the sessionId starts with the cron prefix ("cron-"),
|
|
208
|
+
* the command is delivered through the push channel instead of the
|
|
209
|
+
* WebSocket session, because cron-triggered tool calls have no active
|
|
210
|
+
* WebSocket session. The device receives the push, executes the command,
|
|
211
|
+
* and returns results through the normal WebSocket path — so response
|
|
212
|
+
* listening in the calling tool works unchanged.
|
|
158
213
|
*/
|
|
159
214
|
export async function sendCommand(params) {
|
|
160
|
-
const { config, sessionId, taskId, messageId,
|
|
215
|
+
const { config, sessionId, taskId, messageId, toolCallId } = params;
|
|
216
|
+
const commands = params.commands ?? (params.command ? [params.command] : []);
|
|
217
|
+
if (commands.length === 0) {
|
|
218
|
+
throw new Error("sendCommand requires command or commands.");
|
|
219
|
+
}
|
|
220
|
+
// ── Cron mode: disabled ────────────────────────────────────────
|
|
221
|
+
// sendCommandViaPush is disabled in this version. Cron-triggered
|
|
222
|
+
// tool calls that try to send commands will be rejected.
|
|
223
|
+
if (sessionId.startsWith("cron-") || isCronToolCall(toolCallId)) {
|
|
224
|
+
throw new Error("sendCommandViaPush is disabled in this version");
|
|
225
|
+
}
|
|
226
|
+
// ── Normal mode: WebSocket ─────────────────────────────────────
|
|
161
227
|
// Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
|
|
162
228
|
// fall back to closure-captured values
|
|
163
229
|
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
164
230
|
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
231
|
+
const log = logger.withContext(sessionId, currentTaskId);
|
|
165
232
|
// Build artifact update with command as data
|
|
166
233
|
// Wrap command in commands array as per protocol requirement
|
|
167
234
|
const artifact = {
|
|
@@ -169,19 +236,21 @@ export async function sendCommand(params) {
|
|
|
169
236
|
kind: "artifact-update",
|
|
170
237
|
append: false,
|
|
171
238
|
lastChunk: true,
|
|
172
|
-
final: false,
|
|
239
|
+
final: params.final ?? false,
|
|
173
240
|
artifact: {
|
|
174
241
|
artifactId: uuidv4(),
|
|
175
242
|
parts: [
|
|
176
243
|
{
|
|
177
244
|
kind: "data",
|
|
178
245
|
data: {
|
|
179
|
-
commands
|
|
246
|
+
commands,
|
|
180
247
|
},
|
|
181
248
|
},
|
|
182
249
|
],
|
|
183
250
|
},
|
|
184
251
|
};
|
|
252
|
+
// 对消息内容字段做敏感信息脱敏
|
|
253
|
+
artifact.artifact.parts = redactMessagePayload(artifact.artifact.parts, "parts");
|
|
185
254
|
// Build JSON-RPC response
|
|
186
255
|
const jsonRpcResponse = {
|
|
187
256
|
jsonrpc: "2.0",
|
|
@@ -197,16 +266,70 @@ export async function sendCommand(params) {
|
|
|
197
266
|
taskId: currentTaskId,
|
|
198
267
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
199
268
|
};
|
|
200
|
-
//
|
|
201
|
-
|
|
269
|
+
// Log complete response body
|
|
270
|
+
log.log(`[A2A_COMMAND] Sending command`);
|
|
202
271
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
203
|
-
|
|
272
|
+
log.log(`[A2A_COMMAND] Command sent successfully`);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Send a card (e.g., HTML H5 card) as an artifact update (final=false).
|
|
276
|
+
*
|
|
277
|
+
* Cron-aware: same routing logic as sendCommand.
|
|
278
|
+
*/
|
|
279
|
+
export async function sendCard(params) {
|
|
280
|
+
const { config, sessionId, taskId, messageId, toolCallId } = params;
|
|
281
|
+
// ── Cron mode: route through push channel ──────────────────────
|
|
282
|
+
if (sessionId.startsWith("cron-") || isCronToolCall(toolCallId)) {
|
|
283
|
+
throw new Error("sendCard does not support cron mode");
|
|
284
|
+
}
|
|
285
|
+
// ── Normal mode: WebSocket ─────────────────────────────────────
|
|
286
|
+
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
287
|
+
const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
|
|
288
|
+
const log = logger.withContext(sessionId, currentTaskId);
|
|
289
|
+
// Build artifact update with cardsInfo as data
|
|
290
|
+
const artifact = {
|
|
291
|
+
taskId: currentTaskId,
|
|
292
|
+
kind: "artifact-update",
|
|
293
|
+
append: false,
|
|
294
|
+
lastChunk: true,
|
|
295
|
+
final: params.final ?? false,
|
|
296
|
+
artifact: {
|
|
297
|
+
artifactId: uuidv4(),
|
|
298
|
+
parts: [
|
|
299
|
+
{
|
|
300
|
+
kind: "data",
|
|
301
|
+
data: {
|
|
302
|
+
cardsInfo: params.cardsInfo,
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
// Build JSON-RPC response
|
|
309
|
+
const jsonRpcResponse = {
|
|
310
|
+
jsonrpc: "2.0",
|
|
311
|
+
id: currentMessageId,
|
|
312
|
+
result: artifact,
|
|
313
|
+
};
|
|
314
|
+
// Send via WebSocket
|
|
315
|
+
const wsManager = getXYWebSocketManager(config);
|
|
316
|
+
const outboundMessage = {
|
|
317
|
+
msgType: "agent_response",
|
|
318
|
+
agentId: config.agentId,
|
|
319
|
+
sessionId,
|
|
320
|
+
taskId: currentTaskId,
|
|
321
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
322
|
+
};
|
|
323
|
+
log.log(`[A2A_CARD] Sending card`);
|
|
324
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
325
|
+
log.log(`[A2A_CARD] Card sent successfully`);
|
|
204
326
|
}
|
|
205
327
|
/**
|
|
206
328
|
* Send a clearContext response.
|
|
207
329
|
*/
|
|
208
330
|
export async function sendClearContextResponse(params) {
|
|
209
331
|
const { config, sessionId, messageId } = params;
|
|
332
|
+
const log = logger.withContext(sessionId, "");
|
|
210
333
|
// Build JSON-RPC response for clearContext
|
|
211
334
|
const jsonRpcResponse = {
|
|
212
335
|
jsonrpc: "2.0",
|
|
@@ -232,13 +355,14 @@ export async function sendClearContextResponse(params) {
|
|
|
232
355
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
233
356
|
};
|
|
234
357
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
235
|
-
|
|
358
|
+
log.log(`[CLEAR_CONTEXT] Sent clearContext response`);
|
|
236
359
|
}
|
|
237
360
|
/**
|
|
238
361
|
* Send a tasks/cancel response.
|
|
239
362
|
*/
|
|
240
363
|
export async function sendTasksCancelResponse(params) {
|
|
241
364
|
const { config, sessionId, taskId, messageId } = params;
|
|
365
|
+
const log = logger.withContext(sessionId, taskId);
|
|
242
366
|
// Build JSON-RPC response for tasks/cancel
|
|
243
367
|
// Note: Using any to bypass type check as the response format differs from standard A2A types
|
|
244
368
|
const jsonRpcResponse = {
|
|
@@ -265,13 +389,24 @@ export async function sendTasksCancelResponse(params) {
|
|
|
265
389
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
266
390
|
};
|
|
267
391
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
268
|
-
|
|
392
|
+
log.log(`[TASKS_CANCEL] Sent tasks/cancel response`);
|
|
269
393
|
}
|
|
270
394
|
/**
|
|
271
395
|
* Send a Trigger response with pushData content.
|
|
272
396
|
*/
|
|
273
397
|
export async function sendTriggerResponse(params) {
|
|
274
398
|
const { config, sessionId, taskId, messageId, content } = params;
|
|
399
|
+
const log = logger.withContext(sessionId, taskId);
|
|
400
|
+
// 审批桥接和脱敏
|
|
401
|
+
const bridgedContent = rewriteOutboundApprovalText(sessionId, content);
|
|
402
|
+
const redactedContent = redactSensitiveText(bridgedContent);
|
|
403
|
+
// 对消息内容做敏感信息脱敏
|
|
404
|
+
const artifactParts = redactMessagePayload([
|
|
405
|
+
{
|
|
406
|
+
kind: "text",
|
|
407
|
+
text: bridgedContent,
|
|
408
|
+
},
|
|
409
|
+
], "parts");
|
|
275
410
|
// Build JSON-RPC response for Trigger
|
|
276
411
|
const jsonRpcResponse = {
|
|
277
412
|
jsonrpc: "2.0",
|
|
@@ -284,12 +419,7 @@ export async function sendTriggerResponse(params) {
|
|
|
284
419
|
final: true,
|
|
285
420
|
artifact: {
|
|
286
421
|
artifactId: uuidv4(),
|
|
287
|
-
parts:
|
|
288
|
-
{
|
|
289
|
-
kind: "text",
|
|
290
|
-
text: content,
|
|
291
|
-
},
|
|
292
|
-
],
|
|
422
|
+
parts: artifactParts,
|
|
293
423
|
},
|
|
294
424
|
},
|
|
295
425
|
error: {
|
|
@@ -306,7 +436,7 @@ export async function sendTriggerResponse(params) {
|
|
|
306
436
|
taskId,
|
|
307
437
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
308
438
|
};
|
|
309
|
-
|
|
439
|
+
log.log(`[TRIGGER_RESPONSE] Sending Trigger response, text=${buildTextPreview(redactedContent)}`);
|
|
310
440
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
311
|
-
|
|
441
|
+
log.log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
|
|
312
442
|
}
|
package/dist/src/monitor.js
CHANGED
|
@@ -7,6 +7,7 @@ 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
|
+
import { handleCronQueryEvent } from "./cron-query-handler.js";
|
|
10
11
|
import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
|
|
11
12
|
import { cleanupStaleSessions, getActiveSessionCount, cleanupAllSessions } from "./tools/session-manager.js";
|
|
12
13
|
import { logger } from "./utils/logger.js";
|
|
@@ -51,7 +52,7 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
51
52
|
}
|
|
52
53
|
: undefined;
|
|
53
54
|
// 🔍 Diagnose WebSocket managers before gateway start
|
|
54
|
-
logger.log("
|
|
55
|
+
logger.log("[DIAGNOSTICS] Checking WebSocket managers before gateway start...");
|
|
55
56
|
diagnoseAllManagers();
|
|
56
57
|
// Get WebSocket manager (cached)
|
|
57
58
|
const wsManager = getXYWebSocketManager(account, runtime);
|
|
@@ -77,12 +78,12 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
77
78
|
// Event handlers (defined early so they can be referenced in cleanup)
|
|
78
79
|
const messageHandler = (message, sessionId, serverId) => {
|
|
79
80
|
const messageKey = `${sessionId}::${message.id}`;
|
|
80
|
-
logger.log(`[MONITOR-HANDLER]
|
|
81
|
+
logger.log(`[MONITOR-HANDLER] messageHandler triggered: messageId=${message.id}`);
|
|
81
82
|
// ✅ Report health: received a message
|
|
82
83
|
trackEvent?.();
|
|
83
84
|
// Check for duplicate message handling
|
|
84
85
|
if (activeMessages.has(messageKey)) {
|
|
85
|
-
logger.error(`[MONITOR-HANDLER]
|
|
86
|
+
logger.error(`[MONITOR-HANDLER] WARNING: Duplicate message detected, messageKey=${messageKey}`);
|
|
86
87
|
}
|
|
87
88
|
activeMessages.add(messageKey);
|
|
88
89
|
const task = async () => {
|
|
@@ -103,45 +104,57 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
103
104
|
}
|
|
104
105
|
catch (err) {
|
|
105
106
|
// ✅ Only log error, don't re-throw to prevent gateway restart
|
|
106
|
-
releaseGate();
|
|
107
107
|
logger.error(`XY gateway: error handling message from ${serverId}: ${String(err)}`);
|
|
108
108
|
}
|
|
109
109
|
finally {
|
|
110
|
+
// 🔑 确保门控始终被释放。handleXYMessage 内部会 catch 所有异常
|
|
111
|
+
// 且某些提前返回路径(clearContext、tasks/cancel 等)不会调用
|
|
112
|
+
// onInitComplete,因此必须在 finally 中兜底释放。
|
|
113
|
+
releaseGate();
|
|
110
114
|
// Remove from active messages when done
|
|
111
115
|
activeMessages.delete(messageKey);
|
|
112
116
|
}
|
|
113
117
|
};
|
|
114
118
|
// 🔑 核心改造:检测steer模式
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
119
|
+
// clearContext / tasks/cancel 没有 params,跳过 parseA2AMessage 直接入队
|
|
120
|
+
const messageMethod = message.method;
|
|
121
|
+
if (messageMethod === "clearContext" || messageMethod === "clear_context"
|
|
122
|
+
|| messageMethod === "tasks/cancel" || messageMethod === "tasks_cancel") {
|
|
123
|
+
void enqueue(sessionId, task).catch((err) => {
|
|
124
|
+
logger.error(`XY gateway: queue processing failed: ${String(err)}`);
|
|
125
|
+
activeMessages.delete(messageKey);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
try {
|
|
130
|
+
const parsed = parseA2AMessage(message);
|
|
131
|
+
const steerMode = cfg.messages?.queue?.mode === "steer";
|
|
132
|
+
const hasActiveRun = hasActiveTask(parsed.sessionId);
|
|
133
|
+
if (steerMode && hasActiveRun) {
|
|
134
|
+
// Steer模式且有活跃任务:不入队列,直接并发执行
|
|
135
|
+
logger.log(`[MONITOR-HANDLER] STEER MODE: Executing concurrently for messageKey=${messageKey}`);
|
|
136
|
+
void task().catch((err) => {
|
|
137
|
+
logger.error(`XY gateway: concurrent steer task failed for ${messageKey}: ${String(err)}`);
|
|
138
|
+
activeMessages.delete(messageKey);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// 正常模式:入队列串行执行
|
|
143
|
+
void enqueue(sessionId, task).catch((err) => {
|
|
144
|
+
logger.error(`XY gateway: queue processing failed: ${String(err)}`);
|
|
145
|
+
activeMessages.delete(messageKey);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
128
148
|
}
|
|
129
|
-
|
|
130
|
-
//
|
|
149
|
+
catch (parseErr) {
|
|
150
|
+
// 解析失败,回退到正常队列模式
|
|
151
|
+
logger.error(`[MONITOR-HANDLER] Failed to parse message for steer detection: ${String(parseErr)}`);
|
|
131
152
|
void enqueue(sessionId, task).catch((err) => {
|
|
132
|
-
logger.error(`XY gateway: queue processing failed
|
|
153
|
+
logger.error(`XY gateway: queue processing failed: ${String(err)}`);
|
|
133
154
|
activeMessages.delete(messageKey);
|
|
134
155
|
});
|
|
135
156
|
}
|
|
136
157
|
}
|
|
137
|
-
catch (parseErr) {
|
|
138
|
-
// 解析失败,回退到正常队列模式
|
|
139
|
-
logger.error(`[MONITOR-HANDLER] Failed to parse message for steer detection: ${String(parseErr)}`);
|
|
140
|
-
void enqueue(sessionId, task).catch((err) => {
|
|
141
|
-
logger.error(`XY gateway: queue processing failed for session ${sessionId}: ${String(err)}`);
|
|
142
|
-
activeMessages.delete(messageKey);
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
158
|
};
|
|
146
159
|
const connectedHandler = (serverId) => {
|
|
147
160
|
if (!loggedServers.has(serverId)) {
|
|
@@ -164,9 +177,7 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
164
177
|
logger.error(`XY gateway: ${serverId} error: ${String(err)}`);
|
|
165
178
|
};
|
|
166
179
|
const triggerEventHandler = (context) => {
|
|
167
|
-
logger.log(`[MONITOR]
|
|
168
|
-
logger.log(`[MONITOR] - sessionId: ${context.sessionId}`);
|
|
169
|
-
logger.log(`[MONITOR] - taskId: ${context.taskId}`);
|
|
180
|
+
logger.log(`[MONITOR] Received trigger-event, dispatching to handler...`);
|
|
170
181
|
// 异步处理 Trigger 事件,不阻塞主流程
|
|
171
182
|
handleTriggerEvent(context, cfg, runtime, accountId).catch((err) => {
|
|
172
183
|
logger.error(`[MONITOR] Failed to handle trigger-event:`, err);
|
|
@@ -174,7 +185,9 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
174
185
|
};
|
|
175
186
|
const selfEvolutionHandler = (context) => {
|
|
176
187
|
logger.log(`[MONITOR] Received self-evolution-event, dispatching to handler...`);
|
|
177
|
-
handleSelfEvolutionEvent(context,
|
|
188
|
+
handleSelfEvolutionEvent(context, cfg).catch((err) => {
|
|
189
|
+
logger.error(`[MONITOR] Failed to handle self-evolution-event:`, err);
|
|
190
|
+
});
|
|
178
191
|
};
|
|
179
192
|
const selfEvolutionStateGetHandler = (context) => {
|
|
180
193
|
logger.log(`[MONITOR] Received self-evolution-state-get-event, dispatching to handler...`);
|
|
@@ -186,16 +199,22 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
186
199
|
logger.log(`[MONITOR] Received login-token-event, dispatching to handler...`);
|
|
187
200
|
handleLoginTokenEvent(context, runtime);
|
|
188
201
|
};
|
|
202
|
+
const cronQueryEventHandler = (context) => {
|
|
203
|
+
logger.log(`[MONITOR] Received cron-query-event, dispatching to handler...`);
|
|
204
|
+
handleCronQueryEvent(context, cfg).catch((err) => {
|
|
205
|
+
logger.error(`[MONITOR] Failed to handle cron-query-event:`, err);
|
|
206
|
+
});
|
|
207
|
+
};
|
|
189
208
|
const cleanup = () => {
|
|
190
209
|
logger.log("XY gateway: cleaning up...");
|
|
191
210
|
// 🔍 Diagnose before cleanup
|
|
192
|
-
logger.log("
|
|
211
|
+
logger.log("[DIAGNOSTICS] Checking WebSocket managers before cleanup...");
|
|
193
212
|
diagnoseAllManagers();
|
|
194
213
|
// Stop health check interval
|
|
195
214
|
if (healthCheckInterval) {
|
|
196
215
|
clearInterval(healthCheckInterval);
|
|
197
216
|
healthCheckInterval = null;
|
|
198
|
-
logger.log("
|
|
217
|
+
logger.log("Stopped periodic health check");
|
|
199
218
|
}
|
|
200
219
|
// Remove event handlers to prevent duplicate calls on gateway restart
|
|
201
220
|
wsManager.off("message", messageHandler);
|
|
@@ -206,6 +225,7 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
206
225
|
wsManager.off("self-evolution-event", selfEvolutionHandler);
|
|
207
226
|
wsManager.off("self-evolution-state-get-event", selfEvolutionStateGetHandler);
|
|
208
227
|
wsManager.off("login-token-event", loginTokenEventHandler);
|
|
228
|
+
wsManager.off("cron-query-event", cronQueryEventHandler);
|
|
209
229
|
// ✅ Disconnect the wsManager to prevent connection leaks
|
|
210
230
|
// This is safe because each gateway lifecycle should have clean connections
|
|
211
231
|
wsManager.disconnect();
|
|
@@ -215,9 +235,9 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
215
235
|
cleanupAllSessions();
|
|
216
236
|
loggedServers.clear();
|
|
217
237
|
activeMessages.clear();
|
|
218
|
-
logger.log(`[MONITOR-HANDLER]
|
|
238
|
+
logger.log(`[MONITOR-HANDLER] Cleanup complete, cleared active messages and sessions`);
|
|
219
239
|
// 🔍 Diagnose after cleanup
|
|
220
|
-
logger.log("
|
|
240
|
+
logger.log("[DIAGNOSTICS] Checking WebSocket managers after cleanup...");
|
|
221
241
|
diagnoseAllManagers();
|
|
222
242
|
};
|
|
223
243
|
const handleAbort = async () => {
|
|
@@ -228,7 +248,7 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
228
248
|
if (activeBindings.length > 0) {
|
|
229
249
|
const config = resolveXYConfig(cfg);
|
|
230
250
|
const notificationText = "Gateway即将重启,重启期间可能短暂出现\u201c环境异常\u201d提示,请稍候并耐心重试~";
|
|
231
|
-
logger.log(`[MONITOR]
|
|
251
|
+
logger.log(`[MONITOR] Sending restart notifications to ${activeBindings.length} active session(s)`);
|
|
232
252
|
const sendPromises = activeBindings.map(binding => sendA2AResponse({
|
|
233
253
|
config,
|
|
234
254
|
sessionId: binding.sessionId,
|
|
@@ -238,10 +258,10 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
238
258
|
append: false,
|
|
239
259
|
final: true,
|
|
240
260
|
}).catch(err => {
|
|
241
|
-
logger.error(`[MONITOR] Failed to send restart notification
|
|
261
|
+
logger.error(`[MONITOR] Failed to send restart notification: ${String(err)}`);
|
|
242
262
|
}));
|
|
243
263
|
await Promise.all(sendPromises);
|
|
244
|
-
logger.log(`[MONITOR]
|
|
264
|
+
logger.log(`[MONITOR] Restart notifications sent to ${activeBindings.length} session(s)`);
|
|
245
265
|
}
|
|
246
266
|
else {
|
|
247
267
|
logger.log(`[MONITOR] No active sessions, skipping restart notifications`);
|
|
@@ -269,21 +289,22 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
269
289
|
wsManager.on("self-evolution-event", selfEvolutionHandler);
|
|
270
290
|
wsManager.on("self-evolution-state-get-event", selfEvolutionStateGetHandler);
|
|
271
291
|
wsManager.on("login-token-event", loginTokenEventHandler);
|
|
292
|
+
wsManager.on("cron-query-event", cronQueryEventHandler);
|
|
272
293
|
// Start periodic health check (every 6 hours)
|
|
273
|
-
logger.log("
|
|
294
|
+
logger.log("Starting periodic health check (every 6 hours)...");
|
|
274
295
|
healthCheckInterval = setInterval(() => {
|
|
275
|
-
logger.log("
|
|
296
|
+
logger.log("[HEALTH CHECK] Periodic WebSocket diagnostics...");
|
|
276
297
|
diagnoseAllManagers();
|
|
277
298
|
// Auto-cleanup orphan connections
|
|
278
299
|
const cleaned = cleanupOrphanConnections();
|
|
279
300
|
if (cleaned > 0) {
|
|
280
|
-
logger.log(
|
|
301
|
+
logger.log(`[HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
|
|
281
302
|
}
|
|
282
303
|
// Cleanup stale sessions (older than 10min TTL)
|
|
283
304
|
const cleanedSessions = cleanupStaleSessions();
|
|
284
305
|
const remainingSessions = getActiveSessionCount();
|
|
285
306
|
if (cleanedSessions > 0 || remainingSessions > 0) {
|
|
286
|
-
logger.log(
|
|
307
|
+
logger.log(`[HEALTH CHECK] Sessions: cleaned=${cleanedSessions}, active=${remainingSessions}`);
|
|
287
308
|
}
|
|
288
309
|
// Cleanup stale temp files (older than 24 hours)
|
|
289
310
|
void cleanupStaleTempFiles();
|