@ynhcj/xiaoyi-channel 0.0.159-beta → 0.0.160-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/cron-query-handler.js +1 -1
- package/dist/src/cspl/constants.d.ts +1 -0
- package/dist/src/cspl/constants.js +2 -0
- package/dist/src/cspl/sentinel_hook.js +16 -2
- package/dist/src/tools/send-file-to-user-tool.js +24 -6
- package/dist/src/tools/xiaoyi-add-collection-tool.js +1 -1
- package/dist/src/websocket.js +4 -4
- package/package.json +1 -1
|
@@ -29,6 +29,7 @@ export declare const ENV_FILE_PATH = "/home/sandbox/.openclaw/.xiaoyienv";
|
|
|
29
29
|
export declare const API_URL_SUFFIX = "/celia-claw/v1/rest-api/skill/execute";
|
|
30
30
|
export declare const REQUIRED_ENV_VARS: string[];
|
|
31
31
|
export declare const ALLOWED_TOOLS: string[];
|
|
32
|
+
export declare const STEER_ABORT_MESSAGE = "\u5F53\u524D\u884C\u4E3A\u5B58\u5728\u5B89\u5168\u9690\u60A3\uFF0C\u7EC8\u6B62\u6240\u6709\u64CD\u4F5C\uFF0C\u5E76\u4E14\u5728\u6700\u7EC8\u56DE\u590D\u4E2D\u8BF4\u660E\uFF0C\u8BC6\u522B\u5230\u5F53\u524D\u6D41\u7A0B\u4E2D\u5B58\u5728\u6F5C\u5728\u5B89\u5168\u98CE\u9669\uFF0C\u5DF2\u5E2E\u60A8\u4E2D\u6B62\u5F53\u524D\u6D41\u7A0B\uFF0C\u8BF7\u6E05\u7A7A\u4E0A\u4E0B\u6587\u540E\u91CD\u8BD5";
|
|
32
33
|
export declare const MAX_FILE_COUNT = 10;
|
|
33
34
|
export declare const MAX_COMMAND_LENGTH = 1024;
|
|
34
35
|
export declare const CODE_FILE_EXTENSIONS: string[];
|
|
@@ -28,6 +28,8 @@ export const API_URL_SUFFIX = '/celia-claw/v1/rest-api/skill/execute';
|
|
|
28
28
|
export const REQUIRED_ENV_VARS = ['PERSONAL-API-KEY', 'PERSONAL-UID', 'SERVICE_URL'];
|
|
29
29
|
// 工具白名单:除了web_fetch特殊处理外,只有这些工具才处理result文本
|
|
30
30
|
export const ALLOWED_TOOLS = ['web_fetch', 'read', 'pdf', 'exec', 'message'];
|
|
31
|
+
// Steer 注入话术:CSPL REJECT 时通过 steer 插话打断模型
|
|
32
|
+
export const STEER_ABORT_MESSAGE = '当前行为存在安全隐患,终止所有操作,并且在最终回复中说明,识别到当前流程中存在潜在安全风险,已帮您中止当前流程,请清空上下文后重试';
|
|
31
33
|
// 文件数量限制
|
|
32
34
|
export const MAX_FILE_COUNT = 10;
|
|
33
35
|
// 命令字符串截断长度(字节)
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import { callApi } from './call_api.js';
|
|
6
6
|
import { processText, extractResultText, validateAndTruncateText, parseSecurityResult, handleExecToolInput, handleMessageToolInput, handleOtherToolInput } from './utils.js';
|
|
7
|
-
import { ALLOWED_TOOLS, MAX_TEXT_LENGTH, MAX_TOTAL_LENGTH, MIN_TEXT_LENGTH } from './constants.js';
|
|
7
|
+
import { ALLOWED_TOOLS, MAX_TEXT_LENGTH, MAX_TOTAL_LENGTH, MIN_TEXT_LENGTH, STEER_ABORT_MESSAGE } from './constants.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { getSessionContext } from '../tools/session-manager.js';
|
|
10
|
+
import { tryInjectSteer } from './steer-context.js';
|
|
9
11
|
// 主入口模块
|
|
10
12
|
export default function register(api) {
|
|
11
13
|
api.on("before_tool_call", async (event, ctx) => {
|
|
@@ -70,7 +72,19 @@ export default function register(api) {
|
|
|
70
72
|
const result = parseSecurityResult(response);
|
|
71
73
|
logger.log(`[SENTINEL HOOK] TOOL_OUTPUT response: status=${result.status}.`);
|
|
72
74
|
if (result.status === 'REJECT') {
|
|
73
|
-
logger.warn('[SENTINEL HOOK]
|
|
75
|
+
logger.warn('[SENTINEL HOOK] REJECT detected, attempting steer injection');
|
|
76
|
+
const sessionCtx = ctx.sessionKey ? getSessionContext(ctx.sessionKey) : null;
|
|
77
|
+
if (sessionCtx?.sessionId && sessionCtx?.taskId) {
|
|
78
|
+
await tryInjectSteer({
|
|
79
|
+
sessionId: sessionCtx.sessionId,
|
|
80
|
+
taskId: sessionCtx.taskId,
|
|
81
|
+
message: STEER_ABORT_MESSAGE,
|
|
82
|
+
source: 'cspl',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
logger.warn(`[SENTINEL HOOK] Cannot inject steer: sessionKey=${ctx.sessionKey}, sessionCtx found=${!!sessionCtx}`);
|
|
87
|
+
}
|
|
74
88
|
}
|
|
75
89
|
}
|
|
76
90
|
catch (error) {
|
|
@@ -64,16 +64,22 @@ function normalizeToArray(param) {
|
|
|
64
64
|
/**
|
|
65
65
|
* Download remote file to local temp directory
|
|
66
66
|
*/
|
|
67
|
-
async function downloadRemoteFile(url) {
|
|
67
|
+
async function downloadRemoteFile(url, desiredFilename) {
|
|
68
68
|
try {
|
|
69
69
|
const response = await fetch(url);
|
|
70
70
|
if (!response.ok) {
|
|
71
71
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
72
72
|
}
|
|
73
|
-
//
|
|
74
|
-
let filename
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
// Use desired filename if provided, otherwise extract from URL
|
|
74
|
+
let filename;
|
|
75
|
+
if (desiredFilename) {
|
|
76
|
+
filename = desiredFilename;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
filename = url.split("/").pop() || "downloaded_file";
|
|
80
|
+
// Remove query parameters if present
|
|
81
|
+
filename = filename.split("?")[0];
|
|
82
|
+
}
|
|
77
83
|
// Ensure temp directory exists
|
|
78
84
|
const tempDir = "/tmp/xy_channel";
|
|
79
85
|
await fs.mkdir(tempDir, { recursive: true });
|
|
@@ -119,6 +125,9 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
119
125
|
fileRemoteUrls: {
|
|
120
126
|
description: "公网地址数组,包含用户需要回传的文件的公网地址(会先下载到本地再发送),注意不要对原始url做任何截断(例如裁减掉链接后面的鉴权信息或者修改域名后缀),必须使用上下文中完整的文件地址",
|
|
121
127
|
},
|
|
128
|
+
fileNames: {
|
|
129
|
+
description: "文件名数组,与 fileRemoteUrls 一一对应,用于指定下载后的文件名。必须从 search_file 工具返回结果中提取每个文件的原始文件名,根据uoploadfile工具的顺序传入。",
|
|
130
|
+
},
|
|
122
131
|
},
|
|
123
132
|
},
|
|
124
133
|
async execute(toolCallId, params) {
|
|
@@ -156,6 +165,14 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
156
165
|
throw new Error("fileRemoteUrls array cannot be empty");
|
|
157
166
|
}
|
|
158
167
|
}
|
|
168
|
+
// Normalize fileNames parameter
|
|
169
|
+
let fileNames = [];
|
|
170
|
+
if (params.fileNames) {
|
|
171
|
+
fileNames = normalizeToArray(params.fileNames);
|
|
172
|
+
if (fileNames.length > 0 && fileNames.length !== fileRemoteUrls.length) {
|
|
173
|
+
throw new Error(`fileNames length (${fileNames.length}) must match fileRemoteUrls length (${fileRemoteUrls.length})`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
159
176
|
// Get WebSocket manager
|
|
160
177
|
const wsManager = getXYWebSocketManager(config);
|
|
161
178
|
// Create upload service
|
|
@@ -168,7 +185,8 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
|
|
|
168
185
|
for (let i = 0; i < fileRemoteUrls.length; i++) {
|
|
169
186
|
const remoteUrl = fileRemoteUrls[i];
|
|
170
187
|
try {
|
|
171
|
-
const
|
|
188
|
+
const desiredName = fileNames[i] || undefined;
|
|
189
|
+
const localPath = await downloadRemoteFile(remoteUrl, desiredName);
|
|
172
190
|
allLocalPaths.push(localPath);
|
|
173
191
|
downloadedFiles.push(localPath);
|
|
174
192
|
}
|
|
@@ -32,7 +32,7 @@ export function createXiaoyiAddCollectionTool(ctx) {
|
|
|
32
32
|
● title:非必填字段,数据类型为string,功能描述是标识文件类型数据的文件名称。适用于FILE类型。
|
|
33
33
|
说明:如果dataType为HYPER_LINK或TEXT,则content字段必填且不能为空;如果dataType为IMAGE或FILE,则uri字段必填且不能为空。当用户希望收藏海报、截图等图片类数据时,请将数据以图片IMAGE的形式存入到小艺帮记;当用户希望收藏电子书、笔记、报告、素材、文档、合同、协议、简历、证书、报表、日志、安装包、压缩包等描述的文件时,请将数据以文件FILE的形式存入到小艺帮记。
|
|
34
34
|
当你成功收藏这个数据到小艺帮记后,请在最后显示"已成功把数据添加到[小艺帮记](vassistant://voice/main?page=CollectionPage&jumpHomePageTab=myCollection)",
|
|
35
|
-
请额外注意,如果当前用户的设备是鸿蒙PC,则最后显示的内容需要额外定制,内容是"已成功把数据添加到[小艺帮记](vassistant://voice/main?agentId=celia_collection_share&agentPage=CollectionPage)"
|
|
35
|
+
请额外注意,如果当前用户的设备是鸿蒙PC,则最后显示的内容需要额外定制,内容是"已成功把数据添加到[小艺帮记](vassistant://voice/main?agentId=celia_collection_share&agentPage=CollectionPage&from=push&action=openFlashCardPage)"
|
|
36
36
|
注意:
|
|
37
37
|
a. 操作超时时间为60秒,请勿重复调用此工具
|
|
38
38
|
b. 如果遇到各类调用失败场景,最多只能重试一次,不可以重复调用多次。
|
package/dist/src/websocket.js
CHANGED
|
@@ -602,8 +602,8 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
602
602
|
event: item,
|
|
603
603
|
});
|
|
604
604
|
}
|
|
605
|
-
else if (item.header?.namespace === "
|
|
606
|
-
log.log("[XY]
|
|
605
|
+
else if (item.header?.namespace === "AgentEvent" && item.header?.name === "CronQuery") {
|
|
606
|
+
log.log("[XY] AgentEvent.CronQuery detected, emitting cron-query-event");
|
|
607
607
|
this.emit("cron-query-event", {
|
|
608
608
|
...(item.payload ?? {}),
|
|
609
609
|
sessionId,
|
|
@@ -691,8 +691,8 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
691
691
|
event: item,
|
|
692
692
|
});
|
|
693
693
|
}
|
|
694
|
-
else if (item.header?.namespace === "
|
|
695
|
-
log.log("[XY]
|
|
694
|
+
else if (item.header?.namespace === "AgentEvent" && item.header?.name === "CronQuery") {
|
|
695
|
+
log.log("[XY] AgentEvent.CronQuery detected (wrapped format), emitting cron-query-event");
|
|
696
696
|
this.emit("cron-query-event", {
|
|
697
697
|
...(item.payload ?? {}),
|
|
698
698
|
sessionId: inboundMsg.sessionId || a2aRequest.params?.sessionId,
|