@ynhcj/xiaoyi-channel 0.0.152-beta → 0.0.152-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/index.js +23 -0
- package/dist/src/bot.js +21 -5
- package/dist/src/channel.js +59 -5
- 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 +188 -0
- package/dist/src/cspl/call_api.d.ts +1 -1
- package/dist/src/cspl/call_api.js +2 -2
- package/dist/src/cspl/config.js +30 -10
- package/dist/src/cspl/constants.d.ts +3 -0
- package/dist/src/cspl/constants.js +5 -0
- package/dist/src/cspl/sentinel_hook.js +26 -7
- package/dist/src/cspl/utils.d.ts +9 -3
- package/dist/src/cspl/utils.js +17 -11
- package/dist/src/formatter.d.ts +14 -1
- package/dist/src/formatter.js +31 -8
- package/dist/src/monitor.js +44 -23
- package/dist/src/parser.d.ts +8 -1
- package/dist/src/parser.js +71 -0
- package/dist/src/provider.js +25 -17
- package/dist/src/push.d.ts +11 -1
- package/dist/src/push.js +93 -2
- package/dist/src/reply-dispatcher.js +118 -14
- package/dist/src/self-evolution-handler.d.ts +1 -1
- package/dist/src/self-evolution-handler.js +13 -2
- package/dist/src/tools/agent-as-skill-tool.js +56 -4
- package/dist/src/tools/calendar-tool.js +2 -1
- package/dist/src/tools/call-device-tool.js +0 -3
- package/dist/src/tools/call-phone-tool.js +2 -1
- 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 +2 -1
- package/dist/src/tools/create-all-tools.js +10 -4
- package/dist/src/tools/delete-alarm-tool.js +2 -1
- 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.js +1 -0
- package/dist/src/tools/get-collection-tool-schema.js +1 -1
- package/dist/src/tools/get-device-file-tool-schema.js +2 -3
- package/dist/src/tools/location-tool.js +2 -1
- package/dist/src/tools/modify-alarm-tool.js +2 -1
- package/dist/src/tools/modify-note-tool.js +2 -1
- package/dist/src/tools/note-tool.js +2 -1
- 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 +2 -1
- package/dist/src/tools/save-media-to-gallery-tool.js +2 -1
- package/dist/src/tools/schema-tool-factory.js +1 -1
- package/dist/src/tools/search-alarm-tool.js +2 -1
- package/dist/src/tools/search-calendar-tool.js +2 -1
- package/dist/src/tools/search-contact-tool.js +2 -1
- package/dist/src/tools/search-email-tool.js +4 -3
- package/dist/src/tools/search-file-tool.js +6 -10
- package/dist/src/tools/search-message-tool.js +1 -0
- package/dist/src/tools/search-note-tool.js +2 -1
- package/dist/src/tools/search-photo-gallery-tool.js +4 -3
- 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 +35 -6
- package/dist/src/tools/send-message-tool.js +1 -0
- package/dist/src/tools/session-manager.d.ts +17 -1
- package/dist/src/tools/session-manager.js +73 -0
- package/dist/src/tools/upload-file-tool.js +6 -14
- package/dist/src/tools/upload-photo-tool.js +4 -3
- package/dist/src/tools/xiaoyi-add-collection-tool.js +4 -2
- package/dist/src/tools/xiaoyi-collection-tool.js +3 -2
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +3 -2
- package/dist/src/tools/xiaoyi-gui-tool.js +6 -0
- package/dist/src/types.d.ts +25 -1
- package/dist/src/websocket.d.ts +3 -0
- package/dist/src/websocket.js +207 -15
- package/package.json +1 -1
package/dist/src/cspl/utils.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export declare function extractFilePathsFromCommand(command: string): string[];
|
|
|
14
14
|
export declare function calculateContentHash(content: string): string;
|
|
15
15
|
export declare function getFileSizeInKB(filePath: string): number;
|
|
16
16
|
export declare function adjustContentLength(data: any, api: OpenClawPluginApi, fields: string[]): any;
|
|
17
|
-
export declare function handleExecToolInput(event: any, api: OpenClawPluginApi, sessionId: string): Promise<
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
export declare function handleExecToolInput(event: any, api: OpenClawPluginApi, sessionId: string): Promise<{
|
|
18
|
+
status: 'ACCEPT' | 'REJECT';
|
|
19
|
+
} | null>;
|
|
20
|
+
export declare function handleMessageToolInput(event: any, api: OpenClawPluginApi, sessionId: string): Promise<{
|
|
21
|
+
status: 'ACCEPT' | 'REJECT';
|
|
22
|
+
} | null>;
|
|
23
|
+
export declare function handleOtherToolInput(event: any, api: OpenClawPluginApi, sessionId: string): Promise<{
|
|
24
|
+
status: 'ACCEPT' | 'REJECT';
|
|
25
|
+
} | null>;
|
package/dist/src/cspl/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 版权所有 (c) 华为技术有限公司 2026-2026
|
|
3
3
|
*/
|
|
4
|
-
import { MAX_TEXT_LENGTH, regex, SECURITY_NOTICE, MAX_FILE_COUNT, MAX_COMMAND_LENGTH, CODE_FILE_EXTENSIONS, TOOL_INPUT_DEFAULT, FILE_EXTENSION_REGEX } from './constants.js';
|
|
4
|
+
import { MAX_TEXT_LENGTH, regex, SECURITY_NOTICE, MAX_FILE_COUNT, MAX_COMMAND_LENGTH, CODE_FILE_EXTENSIONS, TOOL_INPUT_DEFAULT, FILE_EXTENSION_REGEX, TOOL_INPUT_ACTION } from './constants.js';
|
|
5
5
|
import crypto from 'crypto';
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import path from 'path';
|
|
@@ -213,13 +213,14 @@ export function adjustContentLength(data, api, fields) {
|
|
|
213
213
|
}
|
|
214
214
|
return adjusted;
|
|
215
215
|
}
|
|
216
|
-
// 发送TOOL_INPUT
|
|
216
|
+
// 发送TOOL_INPUT请求并处理响应,返回扫描结果
|
|
217
217
|
async function sendToolInputRequest(postText, api, sessionId) {
|
|
218
|
-
const response = await callApi(postText, api, sessionId);
|
|
218
|
+
const response = await callApi(postText, api, sessionId, TOOL_INPUT_ACTION);
|
|
219
219
|
const result = parseSecurityResult(response);
|
|
220
220
|
logger.log(`[SENTINEL HOOK] TOOL_INPUT response: status=${result.status}`);
|
|
221
|
+
return result;
|
|
221
222
|
}
|
|
222
|
-
// 处理exec工具的TOOL_INPUT
|
|
223
|
+
// 处理exec工具的TOOL_INPUT数据采集,返回最终扫描结果
|
|
223
224
|
export async function handleExecToolInput(event, api, sessionId) {
|
|
224
225
|
const command = extractInputParams(event, 'exec');
|
|
225
226
|
if (!command) {
|
|
@@ -232,6 +233,7 @@ export async function handleExecToolInput(event, api, sessionId) {
|
|
|
232
233
|
// 场景1:执行代码文件
|
|
233
234
|
logger.log(`[SENTINEL HOOK] Found ${filePaths.length} file(s) in command`);
|
|
234
235
|
const nonExistingFiles = [];
|
|
236
|
+
let lastResult = null;
|
|
235
237
|
for (const filePath of filePaths) {
|
|
236
238
|
if (!fs.existsSync(filePath)) {
|
|
237
239
|
nonExistingFiles.push(filePath);
|
|
@@ -247,7 +249,10 @@ export async function handleExecToolInput(event, api, sessionId) {
|
|
|
247
249
|
const postText = JSON.stringify(adjustedData);
|
|
248
250
|
logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for file: ${path.basename(filePath)}, body length: ${postText.length}`);
|
|
249
251
|
try {
|
|
250
|
-
await sendToolInputRequest(postText, api, sessionId);
|
|
252
|
+
lastResult = await sendToolInputRequest(postText, api, sessionId);
|
|
253
|
+
if (lastResult.status === 'REJECT') {
|
|
254
|
+
return lastResult;
|
|
255
|
+
}
|
|
251
256
|
}
|
|
252
257
|
catch (e) {
|
|
253
258
|
logger.error(`[SENTINEL HOOK] Sending TOOL_INPUT Failed: ${e}`);
|
|
@@ -258,6 +263,7 @@ export async function handleExecToolInput(event, api, sessionId) {
|
|
|
258
263
|
const fileNames = nonExistingFiles.map(f => path.basename(f)).join(', ');
|
|
259
264
|
logger.log(`[SENTINEL HOOK] Non-existing files: ${fileNames}`);
|
|
260
265
|
}
|
|
266
|
+
return lastResult;
|
|
261
267
|
}
|
|
262
268
|
else {
|
|
263
269
|
// 场景2:直接执行代码(heredoc场景)
|
|
@@ -268,10 +274,10 @@ export async function handleExecToolInput(event, api, sessionId) {
|
|
|
268
274
|
const adjustedData = adjustContentLength(toolInputData, api, ['source']);
|
|
269
275
|
const postText = JSON.stringify(adjustedData);
|
|
270
276
|
logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for direct code execution, body length: ${postText.length}`);
|
|
271
|
-
await sendToolInputRequest(postText, api, sessionId);
|
|
277
|
+
return await sendToolInputRequest(postText, api, sessionId);
|
|
272
278
|
}
|
|
273
279
|
}
|
|
274
|
-
// 处理message工具的TOOL_INPUT
|
|
280
|
+
// 处理message工具的TOOL_INPUT数据采集,返回扫描结果
|
|
275
281
|
export async function handleMessageToolInput(event, api, sessionId) {
|
|
276
282
|
const message = extractInputParams(event, 'message');
|
|
277
283
|
if (!message) {
|
|
@@ -285,14 +291,14 @@ export async function handleMessageToolInput(event, api, sessionId) {
|
|
|
285
291
|
const adjustedData = adjustContentLength(toolInputData, api, ['content']);
|
|
286
292
|
const postText = JSON.stringify(adjustedData);
|
|
287
293
|
logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for message, body length: ${postText.length}`);
|
|
288
|
-
await sendToolInputRequest(postText, api, sessionId);
|
|
294
|
+
return await sendToolInputRequest(postText, api, sessionId);
|
|
289
295
|
}
|
|
290
|
-
// 处理其他工具(非 exec 和非 message)的 TOOL_INPUT
|
|
296
|
+
// 处理其他工具(非 exec 和非 message)的 TOOL_INPUT 数据采集,返回扫描结果
|
|
291
297
|
export async function handleOtherToolInput(event, api, sessionId) {
|
|
292
298
|
const params = event.params;
|
|
293
299
|
if (!params) {
|
|
294
300
|
logger.log('[SENTINEL HOOK] No params found for tool');
|
|
295
|
-
return;
|
|
301
|
+
return null;
|
|
296
302
|
}
|
|
297
303
|
logger.log(`[SENTINEL HOOK] Processing other tool input, toolName: ${event.toolName}`);
|
|
298
304
|
// 将 params 序列化为 JSON 字符串
|
|
@@ -305,5 +311,5 @@ export async function handleOtherToolInput(event, api, sessionId) {
|
|
|
305
311
|
const adjustedData = adjustContentLength(toolInputData, api, ['content']);
|
|
306
312
|
const postText = JSON.stringify(adjustedData);
|
|
307
313
|
logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for ${event.toolName}, body length: ${postText.length}`);
|
|
308
|
-
await sendToolInputRequest(postText, api, sessionId);
|
|
314
|
+
return await sendToolInputRequest(postText, api, sessionId);
|
|
309
315
|
}
|
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,10 +64,22 @@ 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>;
|
|
72
85
|
/**
|
package/dist/src/formatter.js
CHANGED
|
@@ -5,6 +5,7 @@ import { logger } from "./utils/logger.js";
|
|
|
5
5
|
import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
6
6
|
import { redactSensitiveText, containsSensitiveInfo } from "./sensitive-redactor.js";
|
|
7
7
|
import { rewriteOutboundApprovalText } from "./approval-bridge.js";
|
|
8
|
+
import { isCronToolCall } from "./tools/session-manager.js";
|
|
8
9
|
// ─────────────────────────────────────────────────────────────
|
|
9
10
|
// 敏感信息脱敏辅助函数
|
|
10
11
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -41,7 +42,7 @@ function buildTextPreview(text) {
|
|
|
41
42
|
* Send an A2A artifact update response.
|
|
42
43
|
*/
|
|
43
44
|
export async function sendA2AResponse(params) {
|
|
44
|
-
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;
|
|
45
46
|
const log = logger.withContext(sessionId, taskId);
|
|
46
47
|
// 审批桥接:将 OpenClaw 的审批提示翻译成用户友好的确认文案
|
|
47
48
|
const bridgedText = text === undefined ? text : rewriteOutboundApprovalText(sessionId, text);
|
|
@@ -96,11 +97,14 @@ export async function sendA2AResponse(params) {
|
|
|
96
97
|
taskId,
|
|
97
98
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
98
99
|
};
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
}
|
|
102
104
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
103
|
-
|
|
105
|
+
if (shouldLog) {
|
|
106
|
+
log.log(`[A2A_RESPONSE] Message sent successfully`);
|
|
107
|
+
}
|
|
104
108
|
}
|
|
105
109
|
/**
|
|
106
110
|
* Send an A2A artifact-update with reasoningText part.
|
|
@@ -199,9 +203,28 @@ export async function sendStatusUpdate(params) {
|
|
|
199
203
|
}
|
|
200
204
|
/**
|
|
201
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.
|
|
202
213
|
*/
|
|
203
214
|
export async function sendCommand(params) {
|
|
204
|
-
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: route through push channel ──────────────────────
|
|
221
|
+
// Detected via: (a) sessionId "cron-" prefix from synthetic session, OR
|
|
222
|
+
// (b) toolCallId marked by before_tool_call hook from openclaw's sessionKey.
|
|
223
|
+
if (sessionId.startsWith("cron-") || isCronToolCall(toolCallId)) {
|
|
224
|
+
const { sendCommandViaPush } = await import("./cron-command.js");
|
|
225
|
+
return sendCommandViaPush({ config, command: commands[0] });
|
|
226
|
+
}
|
|
227
|
+
// ── Normal mode: WebSocket ─────────────────────────────────────
|
|
205
228
|
// Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
|
|
206
229
|
// fall back to closure-captured values
|
|
207
230
|
const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
|
|
@@ -214,14 +237,14 @@ export async function sendCommand(params) {
|
|
|
214
237
|
kind: "artifact-update",
|
|
215
238
|
append: false,
|
|
216
239
|
lastChunk: true,
|
|
217
|
-
final: false,
|
|
240
|
+
final: params.final ?? false,
|
|
218
241
|
artifact: {
|
|
219
242
|
artifactId: uuidv4(),
|
|
220
243
|
parts: [
|
|
221
244
|
{
|
|
222
245
|
kind: "data",
|
|
223
246
|
data: {
|
|
224
|
-
commands
|
|
247
|
+
commands,
|
|
225
248
|
},
|
|
226
249
|
},
|
|
227
250
|
],
|
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";
|
|
@@ -115,35 +116,45 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
115
116
|
}
|
|
116
117
|
};
|
|
117
118
|
// 🔑 核心改造:检测steer模式
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
+
}
|
|
130
148
|
}
|
|
131
|
-
|
|
132
|
-
//
|
|
149
|
+
catch (parseErr) {
|
|
150
|
+
// 解析失败,回退到正常队列模式
|
|
151
|
+
logger.error(`[MONITOR-HANDLER] Failed to parse message for steer detection: ${String(parseErr)}`);
|
|
133
152
|
void enqueue(sessionId, task).catch((err) => {
|
|
134
153
|
logger.error(`XY gateway: queue processing failed: ${String(err)}`);
|
|
135
154
|
activeMessages.delete(messageKey);
|
|
136
155
|
});
|
|
137
156
|
}
|
|
138
157
|
}
|
|
139
|
-
catch (parseErr) {
|
|
140
|
-
// 解析失败,回退到正常队列模式
|
|
141
|
-
logger.error(`[MONITOR-HANDLER] Failed to parse message for steer detection: ${String(parseErr)}`);
|
|
142
|
-
void enqueue(sessionId, task).catch((err) => {
|
|
143
|
-
logger.error(`XY gateway: queue processing failed: ${String(err)}`);
|
|
144
|
-
activeMessages.delete(messageKey);
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
158
|
};
|
|
148
159
|
const connectedHandler = (serverId) => {
|
|
149
160
|
if (!loggedServers.has(serverId)) {
|
|
@@ -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,6 +199,12 @@ 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
|
|
@@ -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();
|
|
@@ -269,6 +289,7 @@ 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
294
|
logger.log("Starting periodic health check (every 6 hours)...");
|
|
274
295
|
healthCheckInterval = setInterval(() => {
|
package/dist/src/parser.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { A2AJsonRpcRequest, A2AMessagePart, A2ADataEvent } from "./types.js";
|
|
1
|
+
import type { A2AJsonRpcRequest, A2AMessagePart, A2ADataEvent, RunCrossTaskContext } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Parsed message information extracted from A2A request.
|
|
4
4
|
* Note: agentId is not extracted from message - it should come from config.
|
|
@@ -30,6 +30,7 @@ export declare function extractFileParts(parts: A2AMessagePart[]): Array<{
|
|
|
30
30
|
* Extract data events from message parts (for tool responses).
|
|
31
31
|
*/
|
|
32
32
|
export declare function extractDataEvents(parts: A2AMessagePart[]): A2ADataEvent[];
|
|
33
|
+
export declare function extractRunCrossTaskContext(parts: A2AMessagePart[]): RunCrossTaskContext | null;
|
|
33
34
|
/**
|
|
34
35
|
* Check if message is a clearContext request.
|
|
35
36
|
*/
|
|
@@ -49,6 +50,12 @@ export declare function extractPushId(parts: A2AMessagePart[]): string | null;
|
|
|
49
50
|
* (same level as push_id).
|
|
50
51
|
*/
|
|
51
52
|
export declare function extractDeviceType(parts: A2AMessagePart[]): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Extract modelName from message parts.
|
|
55
|
+
* Looks for modelName in data parts under variables.systemVariables.modelName
|
|
56
|
+
* (same level as push_id / device_type).
|
|
57
|
+
*/
|
|
58
|
+
export declare function extractModelName(parts: A2AMessagePart[]): string | null;
|
|
52
59
|
/**
|
|
53
60
|
* Extract Trigger event data from message parts.
|
|
54
61
|
* Looks for Trigger events with pushDataId in data parts.
|
package/dist/src/parser.js
CHANGED
|
@@ -45,6 +45,61 @@ export function extractDataEvents(parts) {
|
|
|
45
45
|
.map((part) => part.data.event)
|
|
46
46
|
.filter((event) => event !== undefined);
|
|
47
47
|
}
|
|
48
|
+
export function extractRunCrossTaskContext(parts) {
|
|
49
|
+
const normalizeSentFiles = (value) => {
|
|
50
|
+
if (!Array.isArray(value)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
return value
|
|
54
|
+
.map((item) => {
|
|
55
|
+
if (!item || typeof item !== "object") {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const candidate = item;
|
|
59
|
+
const fileLocalUrls = Array.isArray(candidate.fileLocalUrls)
|
|
60
|
+
? candidate.fileLocalUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
61
|
+
: [];
|
|
62
|
+
const fileRemoteUrls = Array.isArray(candidate.fileRemoteUrls)
|
|
63
|
+
? candidate.fileRemoteUrls.filter((url) => typeof url === "string" && url.length > 0)
|
|
64
|
+
: [];
|
|
65
|
+
const fileNames = Array.isArray(candidate.fileNames)
|
|
66
|
+
? candidate.fileNames.filter((name) => typeof name === "string" && name.length > 0)
|
|
67
|
+
: [];
|
|
68
|
+
if (fileLocalUrls.length === 0 && fileRemoteUrls.length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...(fileLocalUrls.length > 0 ? { fileLocalUrls } : {}),
|
|
73
|
+
...(fileRemoteUrls.length > 0 ? { fileRemoteUrls } : {}),
|
|
74
|
+
...(fileNames.length > 0 && fileNames.length === fileRemoteUrls.length ? { fileNames } : {}),
|
|
75
|
+
};
|
|
76
|
+
})
|
|
77
|
+
.filter((item) => item !== null);
|
|
78
|
+
};
|
|
79
|
+
for (const part of parts) {
|
|
80
|
+
if (part.kind !== "data" || !part.data) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const context = part.data.runCrossTaskContext;
|
|
84
|
+
if (!context || typeof context !== "object") {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const networkId = typeof context.networkId === "string" ? context.networkId : "";
|
|
88
|
+
if (!networkId) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
agentId: typeof context.agentId === "string" ? context.agentId : "",
|
|
93
|
+
sessionId: typeof context.sessionId === "string" ? context.sessionId : "",
|
|
94
|
+
isDistributed: context.isDistributed === true,
|
|
95
|
+
networkId,
|
|
96
|
+
isSupportAgent: context.isSupportAgent !== false,
|
|
97
|
+
sentFiles: normalizeSentFiles(context.sentFiles),
|
|
98
|
+
rawContext: context,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
48
103
|
/**
|
|
49
104
|
* Check if message is a clearContext request.
|
|
50
105
|
*/
|
|
@@ -88,6 +143,22 @@ export function extractDeviceType(parts) {
|
|
|
88
143
|
}
|
|
89
144
|
return null;
|
|
90
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Extract modelName from message parts.
|
|
148
|
+
* Looks for modelName in data parts under variables.systemVariables.modelName
|
|
149
|
+
* (same level as push_id / device_type).
|
|
150
|
+
*/
|
|
151
|
+
export function extractModelName(parts) {
|
|
152
|
+
for (const part of parts) {
|
|
153
|
+
if (part.kind === "data" && part.data) {
|
|
154
|
+
const modelName = part.data.variables?.systemVariables?.modelName;
|
|
155
|
+
if (modelName && typeof modelName === "string") {
|
|
156
|
+
return modelName;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
91
162
|
/**
|
|
92
163
|
* Extract Trigger event data from message parts.
|
|
93
164
|
* Looks for Trigger events with pushDataId in data parts.
|
package/dist/src/provider.js
CHANGED
|
@@ -536,26 +536,28 @@ export const xiaoyiProvider = {
|
|
|
536
536
|
const beforeLen = sp.length;
|
|
537
537
|
// 删除 ## Tooling 与 TOOLS.md 声明之间的内容
|
|
538
538
|
sp = sp.replace(/(## Tooling)[\s\S]*?(TOOLS\.md does not control tool availability; it is user guidance for how to use external tools\.)/, "$1\n\n$2");
|
|
539
|
-
// (1)
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
539
|
+
// (1) Skills 部分:移动到 ## Runtime 之前
|
|
540
|
+
if (sp.includes('## Runtime')) {
|
|
541
|
+
// 提取 ## Skills (mandatory) 到 </available_skills> 作为第一部分
|
|
542
|
+
const skillsMatch = sp.match(/(## Skills \(mandatory\)[\s\S]*?<\/available_skills>)/);
|
|
543
|
+
if (skillsMatch) {
|
|
544
|
+
const part1 = skillsMatch[0];
|
|
545
|
+
sp = sp.replace(part1, '');
|
|
546
|
+
sp = sp.replace('## Runtime', part1 + '\n\n## Runtime');
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// (2) SOUL.md 部分:移动到 ## Silent Replies 之前
|
|
550
|
+
if (sp.includes('## Silent Replies')) {
|
|
551
|
+
// 提取 ## /home/sandbox/.openclaw/workspace/SOUL.md 到 其特定脚注结束标志 的内容作为第二部分
|
|
552
|
+
const soulMatch = sp.match(/(## \/home\/sandbox\/\.openclaw\/workspace\/SOUL\.md[\s\S]*?_This file is yours to evolve\. As you learn who you are, update it\._)/);
|
|
553
|
+
if (soulMatch) {
|
|
554
|
+
const part2 = soulMatch[1].trim();
|
|
550
555
|
sp = sp.replace(soulMatch[1], '');
|
|
551
|
-
|
|
552
|
-
sp = sp.replace(/\n{3,}/g, '\n\n');
|
|
553
|
-
// (3) 将 第二部分 + 第一部分 插入到 ## Runtime 上面
|
|
554
|
-
const combined = (part2 + '\n\n' + part1).trim();
|
|
555
|
-
if (combined && sp.includes('## Runtime')) {
|
|
556
|
-
sp = sp.replace('## Runtime', combined + '\n\n## Runtime');
|
|
556
|
+
sp = sp.replace('## Silent Replies', part2 + '\n\n## Silent Replies');
|
|
557
557
|
}
|
|
558
558
|
}
|
|
559
|
+
// 清理多余空行
|
|
560
|
+
sp = sp.replace(/\n{3,}/g, '\n\n');
|
|
559
561
|
logger.log(`[xiaoyiprovider] system prompt optimized: ${beforeLen} -> ${sp.length}`);
|
|
560
562
|
context.systemPrompt = sp;
|
|
561
563
|
}
|
|
@@ -585,6 +587,12 @@ export const xiaoyiProvider = {
|
|
|
585
587
|
}
|
|
586
588
|
}
|
|
587
589
|
}
|
|
590
|
+
// ── Override model.id if A2A message specified modelName ──
|
|
591
|
+
const modelNameOverride = getCurrentSessionContext()?.modelName;
|
|
592
|
+
if (modelNameOverride) {
|
|
593
|
+
logger.log(`[xiaoyiprovider] overriding model.id: ${model.id} → ${modelNameOverride}`);
|
|
594
|
+
model = { ...model, id: modelNameOverride };
|
|
595
|
+
}
|
|
588
596
|
// ── Retry-capable streaming ──────────────────────────────
|
|
589
597
|
const cronJob = isCronTriggered(context.messages);
|
|
590
598
|
if (cronJob)
|
package/dist/src/push.d.ts
CHANGED
|
@@ -5,9 +5,14 @@ import type { XYChannelConfig } from "./types.js";
|
|
|
5
5
|
*/
|
|
6
6
|
export declare class XYPushService {
|
|
7
7
|
private config;
|
|
8
|
-
private readonly
|
|
8
|
+
private readonly PROD_PUSH_URL;
|
|
9
|
+
private readonly TEST_PUSH_URL;
|
|
9
10
|
private readonly REQUEST_FROM;
|
|
10
11
|
constructor(config: XYChannelConfig);
|
|
12
|
+
/**
|
|
13
|
+
* Resolve push URL: config.pushUrl > inferred from fileUploadUrl > production default.
|
|
14
|
+
*/
|
|
15
|
+
private resolvePushUrl;
|
|
11
16
|
/**
|
|
12
17
|
* Generate a random trace ID for request tracking.
|
|
13
18
|
*/
|
|
@@ -23,6 +28,11 @@ export declare class XYPushService {
|
|
|
23
28
|
* @param pushId - Push ID to use (required)
|
|
24
29
|
*/
|
|
25
30
|
sendPush(content: string, title: string, data?: Record<string, any>, sessionId?: string, pushDataId?: string, pushId?: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Send a push message with command directives embedded directly.
|
|
33
|
+
* Used for cron-triggered commands where pushText is empty and pushType=101.
|
|
34
|
+
*/
|
|
35
|
+
sendPushWithDirectives(pushId: string, sessionId: string, directives: any[]): Promise<void>;
|
|
26
36
|
/**
|
|
27
37
|
* Send a push message with file attachments.
|
|
28
38
|
*/
|
package/dist/src/push.js
CHANGED
|
@@ -8,11 +8,24 @@ import { logger } from "./utils/logger.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export class XYPushService {
|
|
10
10
|
config;
|
|
11
|
-
|
|
11
|
+
PROD_PUSH_URL = "https://hag.cloud.huawei.com/open-ability-agent/v1/agent-webhook";
|
|
12
|
+
TEST_PUSH_URL = "https://lfhagcp.hwcloudtest.cn:58447/open-ability-agent/v1/agent-webhook";
|
|
12
13
|
REQUEST_FROM = "openclaw";
|
|
13
14
|
constructor(config) {
|
|
14
15
|
this.config = config;
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve push URL: config.pushUrl > inferred from fileUploadUrl > production default.
|
|
19
|
+
*/
|
|
20
|
+
resolvePushUrl() {
|
|
21
|
+
if (this.config.pushUrl) {
|
|
22
|
+
return this.config.pushUrl;
|
|
23
|
+
}
|
|
24
|
+
if (this.config.fileUploadUrl?.includes("lfhagmirror")) {
|
|
25
|
+
return this.TEST_PUSH_URL;
|
|
26
|
+
}
|
|
27
|
+
return this.PROD_PUSH_URL;
|
|
28
|
+
}
|
|
16
29
|
/**
|
|
17
30
|
* Generate a random trace ID for request tracking.
|
|
18
31
|
*/
|
|
@@ -30,7 +43,7 @@ export class XYPushService {
|
|
|
30
43
|
* @param pushId - Push ID to use (required)
|
|
31
44
|
*/
|
|
32
45
|
async sendPush(content, title, data, sessionId, pushDataId, pushId) {
|
|
33
|
-
const pushUrl = this.
|
|
46
|
+
const pushUrl = this.resolvePushUrl();
|
|
34
47
|
const traceId = this.generateTraceId();
|
|
35
48
|
// Use provided pushId or fall back to config pushId
|
|
36
49
|
const actualPushId = pushId || this.config.pushId;
|
|
@@ -114,6 +127,84 @@ export class XYPushService {
|
|
|
114
127
|
throw error;
|
|
115
128
|
}
|
|
116
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Send a push message with command directives embedded directly.
|
|
132
|
+
* Used for cron-triggered commands where pushText is empty and pushType=101.
|
|
133
|
+
*/
|
|
134
|
+
async sendPushWithDirectives(pushId, sessionId, directives) {
|
|
135
|
+
const pushUrl = this.resolvePushUrl();
|
|
136
|
+
const traceId = this.generateTraceId();
|
|
137
|
+
logger.log(`[PUSH] Preparing to send push with directives, pushId: ${pushId.substring(0, 20)}...`);
|
|
138
|
+
const requestBody = {
|
|
139
|
+
jsonrpc: "2.0",
|
|
140
|
+
id: randomUUID(),
|
|
141
|
+
result: {
|
|
142
|
+
id: randomUUID(),
|
|
143
|
+
apiId: this.config.apiId,
|
|
144
|
+
pushId,
|
|
145
|
+
pushText: "",
|
|
146
|
+
pushType: 101,
|
|
147
|
+
kind: "task",
|
|
148
|
+
sessionId,
|
|
149
|
+
artifacts: [
|
|
150
|
+
{
|
|
151
|
+
artifactId: randomUUID(),
|
|
152
|
+
parts: [
|
|
153
|
+
{
|
|
154
|
+
kind: "data",
|
|
155
|
+
data: { directives },
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
try {
|
|
163
|
+
const response = await fetch(pushUrl, {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: {
|
|
166
|
+
"Content-Type": "application/json",
|
|
167
|
+
"Accept": "application/json",
|
|
168
|
+
"x-hag-trace-id": traceId,
|
|
169
|
+
"x-uid": this.config.uid,
|
|
170
|
+
"x-api-key": this.config.apiKey,
|
|
171
|
+
"x-request-from": this.REQUEST_FROM,
|
|
172
|
+
},
|
|
173
|
+
body: JSON.stringify(requestBody),
|
|
174
|
+
});
|
|
175
|
+
logger.log(`[PUSH] Response received, HTTP Status: ${response.status} ${response.statusText}`);
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
const errorText = await response.text();
|
|
178
|
+
logger.error(`[PUSH] Push request failed, HTTP Status: ${response.status}`);
|
|
179
|
+
throw new Error(`Push failed: HTTP ${response.status} - ${errorText}`);
|
|
180
|
+
}
|
|
181
|
+
let result;
|
|
182
|
+
try {
|
|
183
|
+
const responseText = await response.text();
|
|
184
|
+
if (!responseText || responseText.trim() === '') {
|
|
185
|
+
logger.error(`[PUSH] Received empty response body`);
|
|
186
|
+
result = {};
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
result = JSON.parse(responseText);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (parseError) {
|
|
193
|
+
logger.error(`[PUSH] Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
194
|
+
throw new Error(`Invalid JSON response from push service: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
195
|
+
}
|
|
196
|
+
logger.log(`[PUSH] Push message sent successfully, Trace ID: ${traceId}`);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (error instanceof Error) {
|
|
200
|
+
logger.error(`[PUSH] Failed to send push message: ${error.name} - ${error.message}`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
logger.error(`[PUSH] Failed to send push message:`, error);
|
|
204
|
+
}
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
117
208
|
/**
|
|
118
209
|
* Send a push message with file attachments.
|
|
119
210
|
*/
|