@ynhcj/xiaoyi-channel 0.0.167-beta → 0.0.169-beta
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 +4 -4
- package/dist/src/cspl/sentinel_hook.js +9 -4
- package/dist/src/cspl/utils.d.ts +9 -3
- package/dist/src/cspl/utils.js +15 -9
- package/dist/src/monitor.js +35 -23
- package/dist/src/self-evolution-handler.d.ts +1 -1
- package/dist/src/self-evolution-handler.js +12 -1
- package/dist/src/tools/check-plugin-privilege-tool.js +3 -1
- package/dist/src/types.d.ts +3 -1
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -32,9 +32,9 @@ export async function handleXYMessage(params) {
|
|
|
32
32
|
try {
|
|
33
33
|
// Check for special messages BEFORE parsing (these have different param structures)
|
|
34
34
|
const messageMethod = message.method;
|
|
35
|
-
// Handle clearContext messages (
|
|
35
|
+
// Handle clearContext messages (sessionId at top level, no params)
|
|
36
36
|
if (messageMethod === "clearContext" || messageMethod === "clear_context") {
|
|
37
|
-
const sessionId = message.params?.sessionId;
|
|
37
|
+
const sessionId = message.sessionId ?? message.params?.sessionId;
|
|
38
38
|
if (!sessionId) {
|
|
39
39
|
throw new Error("clearContext request missing sessionId in params");
|
|
40
40
|
}
|
|
@@ -48,9 +48,9 @@ export async function handleXYMessage(params) {
|
|
|
48
48
|
});
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
-
// Handle tasks/cancel messages
|
|
51
|
+
// Handle tasks/cancel messages (sessionId at top level, no params)
|
|
52
52
|
if (messageMethod === "tasks/cancel" || messageMethod === "tasks_cancel") {
|
|
53
|
-
const sessionId = message.params?.sessionId;
|
|
53
|
+
const sessionId = message.sessionId ?? message.params?.sessionId;
|
|
54
54
|
const taskId = message.params?.id || message.id;
|
|
55
55
|
if (!sessionId) {
|
|
56
56
|
throw new Error("tasks/cancel request missing sessionId in params");
|
|
@@ -15,16 +15,21 @@ export default function register(api) {
|
|
|
15
15
|
// 生成sessionID
|
|
16
16
|
const sessionId = (event.runId?.replace(/-/g, '') || crypto.randomBytes(16).toString('hex'));
|
|
17
17
|
logger.log(`[SENTINEL HOOK] Generated Session ID: ${sessionId}`);
|
|
18
|
-
// 处理 TOOL_INPUT
|
|
18
|
+
// 处理 TOOL_INPUT 数据采集、发送数据,根据扫描结果决定是否阻塞
|
|
19
19
|
try {
|
|
20
|
+
let scanResult = null;
|
|
20
21
|
if (event.toolName === 'exec') {
|
|
21
|
-
await handleExecToolInput(event, api, sessionId);
|
|
22
|
+
scanResult = await handleExecToolInput(event, api, sessionId);
|
|
22
23
|
}
|
|
23
24
|
else if (event.toolName === 'message') {
|
|
24
|
-
await handleMessageToolInput(event, api, sessionId);
|
|
25
|
+
scanResult = await handleMessageToolInput(event, api, sessionId);
|
|
25
26
|
}
|
|
26
27
|
else {
|
|
27
|
-
await handleOtherToolInput(event, api, sessionId);
|
|
28
|
+
scanResult = await handleOtherToolInput(event, api, sessionId);
|
|
29
|
+
}
|
|
30
|
+
if (scanResult?.status === 'REJECT') {
|
|
31
|
+
logger.warn(`[SENTINEL HOOK] TOOL_INPUT REJECT, blocking tool call: ${event.toolName}`);
|
|
32
|
+
return { block: true, blockReason: `安全扫描检测到风险,已阻止工具调用: ${event.toolName}` };
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
catch (error) {
|
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
|
@@ -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
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/monitor.js
CHANGED
|
@@ -116,35 +116,45 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
116
116
|
}
|
|
117
117
|
};
|
|
118
118
|
// 🔑 核心改造:检测steer模式
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
}
|
|
131
148
|
}
|
|
132
|
-
|
|
133
|
-
//
|
|
149
|
+
catch (parseErr) {
|
|
150
|
+
// 解析失败,回退到正常队列模式
|
|
151
|
+
logger.error(`[MONITOR-HANDLER] Failed to parse message for steer detection: ${String(parseErr)}`);
|
|
134
152
|
void enqueue(sessionId, task).catch((err) => {
|
|
135
153
|
logger.error(`XY gateway: queue processing failed: ${String(err)}`);
|
|
136
154
|
activeMessages.delete(messageKey);
|
|
137
155
|
});
|
|
138
156
|
}
|
|
139
157
|
}
|
|
140
|
-
catch (parseErr) {
|
|
141
|
-
// 解析失败,回退到正常队列模式
|
|
142
|
-
logger.error(`[MONITOR-HANDLER] Failed to parse message for steer detection: ${String(parseErr)}`);
|
|
143
|
-
void enqueue(sessionId, task).catch((err) => {
|
|
144
|
-
logger.error(`XY gateway: queue processing failed: ${String(err)}`);
|
|
145
|
-
activeMessages.delete(messageKey);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
158
|
};
|
|
149
159
|
const connectedHandler = (serverId) => {
|
|
150
160
|
if (!loggedServers.has(serverId)) {
|
|
@@ -175,7 +185,9 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
175
185
|
};
|
|
176
186
|
const selfEvolutionHandler = (context) => {
|
|
177
187
|
logger.log(`[MONITOR] Received self-evolution-event, dispatching to handler...`);
|
|
178
|
-
handleSelfEvolutionEvent(context,
|
|
188
|
+
handleSelfEvolutionEvent(context, cfg).catch((err) => {
|
|
189
|
+
logger.error(`[MONITOR] Failed to handle self-evolution-event:`, err);
|
|
190
|
+
});
|
|
179
191
|
};
|
|
180
192
|
const selfEvolutionStateGetHandler = (context) => {
|
|
181
193
|
logger.log(`[MONITOR] Received self-evolution-state-get-event, dispatching to handler...`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { XYWebSocketManager } from "./websocket.js";
|
|
2
|
-
export declare function handleSelfEvolutionEvent(context: any,
|
|
2
|
+
export declare function handleSelfEvolutionEvent(context: any, cfg: any): Promise<void>;
|
|
3
3
|
/**
|
|
4
4
|
* 读取 .xiaoyiruntime 中的 selfEvolutionState 并直接通过 wsManager 下发指令回复设备
|
|
5
5
|
* 参考trigger实现:直接使用当前已连接的 wsManager 发送消息,避免 getXYWebSocketManager 返回未连接实例
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "fs";
|
|
2
2
|
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import { resolveXYConfig } from "./config.js";
|
|
4
|
+
import { sendA2AResponse } from "./formatter.js";
|
|
3
5
|
import { logger } from "./utils/logger.js";
|
|
4
6
|
const XIAOYIRUNTIME_PATH = "/home/sandbox/.openclaw/.xiaoyiruntime";
|
|
5
|
-
export function handleSelfEvolutionEvent(context,
|
|
7
|
+
export async function handleSelfEvolutionEvent(context, cfg) {
|
|
6
8
|
try {
|
|
7
9
|
const state = context.event?.payload?.selfEvolutionState;
|
|
8
10
|
if (typeof state !== "string") {
|
|
9
11
|
logger.error("[SELF_EVOLUTION] invalid payload: missing selfEvolutionState");
|
|
10
12
|
return;
|
|
11
13
|
}
|
|
14
|
+
const sessionId = context.sessionId ?? "";
|
|
15
|
+
const taskId = context.taskId ?? sessionId;
|
|
16
|
+
const messageId = context.messageId ?? uuidv4();
|
|
17
|
+
const config = resolveXYConfig(cfg);
|
|
12
18
|
logger.log(`[SELF_EVOLUTION] received state: ${state}`);
|
|
13
19
|
let content;
|
|
14
20
|
try {
|
|
@@ -19,6 +25,8 @@ export function handleSelfEvolutionEvent(context, runtime) {
|
|
|
19
25
|
logger.log(`[SELF_EVOLUTION] ${XIAOYIRUNTIME_PATH} not found, creating new file`);
|
|
20
26
|
writeFileSync(XIAOYIRUNTIME_PATH, `selfEvolutionState=${state}\n`, "utf-8");
|
|
21
27
|
logger.log(`[SELF_EVOLUTION] wrote selfEvolutionState=${state}`);
|
|
28
|
+
await sendA2AResponse({ config, sessionId, taskId, messageId, text: "", append: false, final: true });
|
|
29
|
+
logger.log(`[SELF_EVOLUTION] Sent final response (empty, stream end)`);
|
|
22
30
|
return;
|
|
23
31
|
}
|
|
24
32
|
const lines = content.split("\n");
|
|
@@ -40,6 +48,9 @@ export function handleSelfEvolutionEvent(context, runtime) {
|
|
|
40
48
|
writeFileSync(XIAOYIRUNTIME_PATH, updated.join("\n"), "utf-8");
|
|
41
49
|
}
|
|
42
50
|
logger.log(`[SELF_EVOLUTION] updated selfEvolutionState=${state} in ${XIAOYIRUNTIME_PATH}`);
|
|
51
|
+
// Reply with final empty response to acknowledge the state update
|
|
52
|
+
await sendA2AResponse({ config, sessionId, taskId, messageId, text: "", append: false, final: true });
|
|
53
|
+
logger.log(`[SELF_EVOLUTION] Sent final response (empty, stream end)`);
|
|
43
54
|
}
|
|
44
55
|
catch (err) {
|
|
45
56
|
logger.error("[SELF_EVOLUTION] failed to handle event:", err);
|
|
@@ -62,7 +62,9 @@ export function createCheckPluginPrivilegeTool(ctx) {
|
|
|
62
62
|
"SendShortMessage(发送短信), " +
|
|
63
63
|
"StartCall(打电话)。" +
|
|
64
64
|
"【多次调用】如果用户的定时任务指令中涉及多个端侧工具,则依次分别调用此工具检查每个工具的权限。如果调用超时失败,最多重试一次。" +
|
|
65
|
-
"【回复约束】如果工具返回没有授权或其他报错,只需要完整描述没有授权或其他报错内容即可,不需要主动给用户提供解决方案。"
|
|
65
|
+
"【回复约束】如果工具返回没有授权或其他报错,只需要完整描述没有授权或其他报错内容即可,不需要主动给用户提供解决方案。" +
|
|
66
|
+
"【使用约束1】只要是创建定时任务且涉及端插件的使用,则必须调用此工具检查权限" +
|
|
67
|
+
"【使用约束2】如果是定时任务执行过程中,禁止调用此工具,此工具仅在创建定时任务时按需调用",
|
|
66
68
|
parameters: {
|
|
67
69
|
type: "object",
|
|
68
70
|
properties: {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -13,8 +13,10 @@ export interface XYChannelConfig {
|
|
|
13
13
|
export interface A2AJsonRpcRequest {
|
|
14
14
|
jsonrpc: "2.0";
|
|
15
15
|
method: string;
|
|
16
|
-
params
|
|
16
|
+
params?: A2ARequestParams;
|
|
17
17
|
id: string;
|
|
18
|
+
/** Top-level sessionId for clearContext/tasks/cancel (no params field) */
|
|
19
|
+
sessionId?: string;
|
|
18
20
|
}
|
|
19
21
|
export interface A2AJsonRpcResponse {
|
|
20
22
|
jsonrpc: "2.0";
|