@ynhcj/xiaoyi-channel 0.0.140-next → 0.0.141-next
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cspl/config.js +12 -31
- package/dist/src/cspl/configs.json +10 -0
- package/dist/src/cspl/sentinel_hook.js +13 -12
- package/dist/src/cspl/utils.js +18 -18
- package/package.json +1 -1
package/dist/src/cspl/config.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* 版权所有 (c) 华为技术有限公司 2026-2026
|
|
3
3
|
*/
|
|
4
4
|
import fs from 'fs';
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
5
|
+
import { ENV_FILE_PATH, REQUIRED_ENV_VARS } from './constants.js';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
import defaultConfig from './configs.json' with { type: 'json' };
|
|
7
8
|
let cachedConfig = null;
|
|
8
9
|
function readEnvFile() {
|
|
9
10
|
if (!fs.existsSync(ENV_FILE_PATH)) {
|
|
@@ -40,45 +41,25 @@ export function getConfig(api) {
|
|
|
40
41
|
if (cachedConfig) {
|
|
41
42
|
return cachedConfig;
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
throw new Error(`Config file not found: ${CONFIG_FILE_NAME}`);
|
|
46
|
-
}
|
|
47
|
-
let configData;
|
|
48
|
-
try {
|
|
49
|
-
configData = fs.readFileSync(configPath, 'utf-8');
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
throw new Error(`Failed to read config file: ${CONFIG_FILE_NAME}.`);
|
|
53
|
-
}
|
|
54
|
-
let parsedConfig;
|
|
55
|
-
try {
|
|
56
|
-
parsedConfig = JSON.parse(configData);
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
throw new Error(`Failed to parse config file: ${CONFIG_FILE_NAME}.`);
|
|
60
|
-
}
|
|
61
|
-
if (!parsedConfig || typeof parsedConfig !== 'object') {
|
|
62
|
-
throw new Error(`Invalid config structure: ${CONFIG_FILE_NAME}. Expected an object.`);
|
|
63
|
-
}
|
|
64
|
-
const config = parsedConfig;
|
|
44
|
+
// Use imported JSON (bundled at compile time, no runtime file read needed)
|
|
45
|
+
const config = { ...defaultConfig };
|
|
65
46
|
if (!config.api || typeof config.api !== 'object') {
|
|
66
|
-
throw new Error(`Invalid config: missing or invalid 'api' section
|
|
47
|
+
throw new Error(`Invalid config: missing or invalid 'api' section`);
|
|
67
48
|
}
|
|
68
49
|
if (!config.api.timeout || typeof config.api.timeout !== 'number') {
|
|
69
|
-
throw new Error(`Invalid config: missing or invalid 'api.timeout'
|
|
50
|
+
throw new Error(`Invalid config: missing or invalid 'api.timeout'`);
|
|
70
51
|
}
|
|
71
52
|
if (!config.skillId || typeof config.skillId !== 'string') {
|
|
72
|
-
throw new Error(`Invalid config: missing or invalid 'skillId'
|
|
53
|
+
throw new Error(`Invalid config: missing or invalid 'skillId'`);
|
|
73
54
|
}
|
|
74
55
|
if (!config.requestFrom || typeof config.requestFrom !== 'string') {
|
|
75
|
-
throw new Error(`Invalid config: missing or invalid 'requestFrom'
|
|
56
|
+
throw new Error(`Invalid config: missing or invalid 'requestFrom'`);
|
|
76
57
|
}
|
|
77
58
|
if (!config.textSource || typeof config.textSource !== 'string') {
|
|
78
|
-
throw new Error(`Invalid config: missing or invalid 'textSource'
|
|
59
|
+
throw new Error(`Invalid config: missing or invalid 'textSource'`);
|
|
79
60
|
}
|
|
80
61
|
if (!config.action || typeof config.action !== 'string') {
|
|
81
|
-
throw new Error(`Invalid config: missing or invalid 'action'
|
|
62
|
+
throw new Error(`Invalid config: missing or invalid 'action'`);
|
|
82
63
|
}
|
|
83
64
|
let env;
|
|
84
65
|
try {
|
|
@@ -104,6 +85,6 @@ export function getConfig(api) {
|
|
|
104
85
|
config.uid = personalUid.trim();
|
|
105
86
|
config.api.url = serviceUrl.trim();
|
|
106
87
|
cachedConfig = config;
|
|
107
|
-
|
|
88
|
+
logger.log(`[SENTINEL HOOK] Config loaded successfully`);
|
|
108
89
|
return cachedConfig;
|
|
109
90
|
}
|
|
@@ -5,13 +5,14 @@ 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
7
|
import { ALLOWED_TOOLS, MAX_TEXT_LENGTH, MAX_TOTAL_LENGTH, MIN_TEXT_LENGTH } from './constants.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
8
9
|
// 主入口模块
|
|
9
10
|
export default function register(api) {
|
|
10
11
|
api.on("before_tool_call", async (event, ctx) => {
|
|
11
|
-
|
|
12
|
+
logger.log(`[SENTINEL HOOK] before_tool_call_event toolName: ${event.toolName}`);
|
|
12
13
|
// 生成sessionID
|
|
13
14
|
const sessionId = (event.runId?.replace(/-/g, '') || crypto.randomBytes(16).toString('hex'));
|
|
14
|
-
|
|
15
|
+
logger.log(`[SENTINEL HOOK] Generated Session ID: ${sessionId}`);
|
|
15
16
|
// 处理 TOOL_INPUT 数据采集、发送数据
|
|
16
17
|
try {
|
|
17
18
|
if (event.toolName === 'exec') {
|
|
@@ -25,7 +26,7 @@ export default function register(api) {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
catch (error) {
|
|
28
|
-
|
|
29
|
+
logger.error(`[SENTINEL HOOK] Extracted TOOL_INPUT data processing exception: ${error}`);
|
|
29
30
|
}
|
|
30
31
|
});
|
|
31
32
|
api.on("after_tool_call", async (event, ctx) => {
|
|
@@ -34,19 +35,19 @@ export default function register(api) {
|
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
36
37
|
try {
|
|
37
|
-
|
|
38
|
+
logger.log(`[SENTINEL HOOK] after_tool_call_event toolName: ${event.toolName}`);
|
|
38
39
|
// 生成sessionID
|
|
39
40
|
const sessionId = (event.runId?.replace(/-/g, '') || crypto.randomBytes(16).toString('hex'));
|
|
40
|
-
|
|
41
|
+
logger.log(`[SENTINEL HOOK] Generated Session ID: ${sessionId}`);
|
|
41
42
|
// 处理TOOL_OUTPUT数据采集(保持现有逻辑)
|
|
42
43
|
const resultText = extractResultText(event, event.toolName);
|
|
43
44
|
const resultTextLength = resultText.length;
|
|
44
45
|
if (resultTextLength > MAX_TOTAL_LENGTH) {
|
|
45
|
-
|
|
46
|
+
logger.warn(`[SENTINEL HOOK] Text exceeds ${MAX_TOTAL_LENGTH} character limit. Actual length: ${resultTextLength}`);
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
48
49
|
if (resultTextLength <= MIN_TEXT_LENGTH) {
|
|
49
|
-
|
|
50
|
+
logger.log("[SENTINEL HOOK] No valid information at collection point");
|
|
50
51
|
return;
|
|
51
52
|
}
|
|
52
53
|
// 处理和验证文本
|
|
@@ -59,17 +60,17 @@ export default function register(api) {
|
|
|
59
60
|
const { text: filterText, truncated } = validateAndTruncateText(originText, MAX_TEXT_LENGTH - diff_length);
|
|
60
61
|
if (truncated) {
|
|
61
62
|
questionText.output[0].content = `${filterText}`;
|
|
62
|
-
|
|
63
|
+
logger.warn(`[SENTINEL HOOK] postText exceeds ${MAX_TEXT_LENGTH}.`);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
const postText = JSON.stringify(questionText);
|
|
66
|
-
|
|
67
|
+
logger.log(`[SENTINEL HOOK] Content extracted successfully. Length: ${postText.length}`);
|
|
67
68
|
try {
|
|
68
69
|
const response = await callApi(postText, api, sessionId);
|
|
69
70
|
const result = parseSecurityResult(response);
|
|
70
|
-
|
|
71
|
+
logger.log(`[SENTINEL HOOK] TOOL_OUTPUT response: status=${result.status}.`);
|
|
71
72
|
if (result.status === 'REJECT') {
|
|
72
|
-
|
|
73
|
+
logger.warn('[SENTINEL HOOK] Interrupt handler');
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
catch (error) {
|
|
@@ -77,7 +78,7 @@ export default function register(api) {
|
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
catch (error) {
|
|
80
|
-
|
|
81
|
+
logger.error(`[SENTINEL HOOK] Extracted TOOL_OUTPUT data processing exception: ${error}`);
|
|
81
82
|
}
|
|
82
83
|
});
|
|
83
84
|
}
|
package/dist/src/cspl/utils.js
CHANGED
|
@@ -8,6 +8,7 @@ import path from 'path';
|
|
|
8
8
|
import { Buffer } from 'buffer';
|
|
9
9
|
import { callApi } from './call_api.js';
|
|
10
10
|
import { uploadFileToObsMain } from './upload_file.js';
|
|
11
|
+
import { logger } from '../utils/logger.js';
|
|
11
12
|
// 文本过滤函数:仅保留中文、英文、数字、标点符号
|
|
12
13
|
export function filterText(text) {
|
|
13
14
|
if (!text)
|
|
@@ -50,7 +51,7 @@ export function processText(resultText, api) {
|
|
|
50
51
|
// 检查是否超过4096字符限制,进行截断
|
|
51
52
|
const { text: finalText, truncated } = validateAndTruncateText(questionText, MAX_TEXT_LENGTH);
|
|
52
53
|
if (truncated) {
|
|
53
|
-
|
|
54
|
+
logger.warn(`[SENTINEL HOOK] filterText exceeds ${MAX_TEXT_LENGTH}. Original length: ${questionText.length}`);
|
|
54
55
|
}
|
|
55
56
|
return finalText;
|
|
56
57
|
}
|
|
@@ -193,12 +194,12 @@ export function adjustContentLength(data, api, fields) {
|
|
|
193
194
|
if (currentFieldValue && typeof currentFieldValue === 'string' && currentFieldValue.length > overSize) {
|
|
194
195
|
// 从字段头部开始截断
|
|
195
196
|
adjusted[fieldName] = currentFieldValue.substring(0, currentFieldValue.length - overSize);
|
|
196
|
-
|
|
197
|
+
logger.warn(`[SENTINEL HOOK] Field "${fieldName}" truncated by ${overSize} characters to fit ${MAX_TEXT_LENGTH} limit`);
|
|
197
198
|
}
|
|
198
199
|
else {
|
|
199
200
|
// 字段太短,清空字段
|
|
200
201
|
adjusted[fieldName] = '';
|
|
201
|
-
|
|
202
|
+
logger.warn(`[SENTINEL HOOK] Field "${fieldName}" cleared as it cannot fit within size limit`);
|
|
202
203
|
}
|
|
203
204
|
// 检查是否满足要求
|
|
204
205
|
bodyStr = JSON.stringify(adjusted);
|
|
@@ -216,21 +217,20 @@ export function adjustContentLength(data, api, fields) {
|
|
|
216
217
|
async function sendToolInputRequest(postText, api, sessionId) {
|
|
217
218
|
const response = await callApi(postText, api, sessionId);
|
|
218
219
|
const result = parseSecurityResult(response);
|
|
219
|
-
|
|
220
|
+
logger.log(`[SENTINEL HOOK] TOOL_INPUT response: status=${result.status}`);
|
|
220
221
|
}
|
|
221
222
|
// 处理exec工具的TOOL_INPUT数据采集
|
|
222
223
|
export async function handleExecToolInput(event, api, sessionId) {
|
|
223
224
|
const command = extractInputParams(event, 'exec');
|
|
224
225
|
if (!command) {
|
|
225
|
-
|
|
226
|
+
logger.log('[SENTINEL HOOK] No command found for exec tool');
|
|
226
227
|
return null;
|
|
227
228
|
}
|
|
228
|
-
//api.logger.info(`[SENTINEL HOOK] Processing exec tool input, command length: ${command.length}`);
|
|
229
229
|
// 解析命令提取文件路径
|
|
230
230
|
const filePaths = extractFilePathsFromCommand(command);
|
|
231
231
|
if (filePaths.length > 0) {
|
|
232
232
|
// 场景1:执行代码文件
|
|
233
|
-
|
|
233
|
+
logger.log(`[SENTINEL HOOK] Found ${filePaths.length} file(s) in command`);
|
|
234
234
|
const nonExistingFiles = [];
|
|
235
235
|
for (const filePath of filePaths) {
|
|
236
236
|
if (!fs.existsSync(filePath)) {
|
|
@@ -245,29 +245,29 @@ export async function handleExecToolInput(event, api, sessionId) {
|
|
|
245
245
|
source: command, content: fileContent };
|
|
246
246
|
const adjustedData = adjustContentLength(toolInputData, api, ['content', 'source']);
|
|
247
247
|
const postText = JSON.stringify(adjustedData);
|
|
248
|
-
|
|
248
|
+
logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for file: ${path.basename(filePath)}, body length: ${postText.length}`);
|
|
249
249
|
try {
|
|
250
250
|
await sendToolInputRequest(postText, api, sessionId);
|
|
251
251
|
}
|
|
252
252
|
catch (e) {
|
|
253
|
-
|
|
253
|
+
logger.error(`[SENTINEL HOOK] Sending TOOL_INPUT Failed: ${e}`);
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
// 输出不存在的文件列表
|
|
257
257
|
if (nonExistingFiles.length > 0) {
|
|
258
258
|
const fileNames = nonExistingFiles.map(f => path.basename(f)).join(', ');
|
|
259
|
-
|
|
259
|
+
logger.log(`[SENTINEL HOOK] Non-existing files: ${fileNames}`);
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
262
|
else {
|
|
263
263
|
// 场景2:直接执行代码(heredoc场景)
|
|
264
|
-
|
|
264
|
+
logger.log('[SENTINEL HOOK] No code files found in command, treating as direct code execution');
|
|
265
265
|
const commandHash = calculateContentHash(command);
|
|
266
266
|
const commandSizeKB = Math.ceil(Buffer.byteLength(command, 'utf8') / 1024);
|
|
267
267
|
const toolInputData = { ...TOOL_INPUT_DEFAULT, tool: 'exec', hash: commandHash, size: commandSizeKB, source: command };
|
|
268
268
|
const adjustedData = adjustContentLength(toolInputData, api, ['source']);
|
|
269
269
|
const postText = JSON.stringify(adjustedData);
|
|
270
|
-
|
|
270
|
+
logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for direct code execution, body length: ${postText.length}`);
|
|
271
271
|
await sendToolInputRequest(postText, api, sessionId);
|
|
272
272
|
}
|
|
273
273
|
}
|
|
@@ -275,26 +275,26 @@ export async function handleExecToolInput(event, api, sessionId) {
|
|
|
275
275
|
export async function handleMessageToolInput(event, api, sessionId) {
|
|
276
276
|
const message = extractInputParams(event, 'message');
|
|
277
277
|
if (!message) {
|
|
278
|
-
|
|
278
|
+
logger.log('[SENTINEL HOOK] No message found for message tool');
|
|
279
279
|
return null;
|
|
280
280
|
}
|
|
281
|
-
|
|
281
|
+
logger.log(`[SENTINEL HOOK] Processing message tool input, message length: ${message.length}`);
|
|
282
282
|
const messageHash = calculateContentHash(message);
|
|
283
283
|
const messageSizeKB = Math.ceil(Buffer.byteLength(message, 'utf8') / 1024);
|
|
284
284
|
const toolInputData = { ...TOOL_INPUT_DEFAULT, tool: 'message', hash: messageHash, size: messageSizeKB, content: message };
|
|
285
285
|
const adjustedData = adjustContentLength(toolInputData, api, ['content']);
|
|
286
286
|
const postText = JSON.stringify(adjustedData);
|
|
287
|
-
|
|
287
|
+
logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for message, body length: ${postText.length}`);
|
|
288
288
|
await sendToolInputRequest(postText, api, sessionId);
|
|
289
289
|
}
|
|
290
290
|
// 处理其他工具(非 exec 和非 message)的 TOOL_INPUT 数据采集
|
|
291
291
|
export async function handleOtherToolInput(event, api, sessionId) {
|
|
292
292
|
const params = event.params;
|
|
293
293
|
if (!params) {
|
|
294
|
-
|
|
294
|
+
logger.log('[SENTINEL HOOK] No params found for tool');
|
|
295
295
|
return;
|
|
296
296
|
}
|
|
297
|
-
|
|
297
|
+
logger.log(`[SENTINEL HOOK] Processing other tool input, toolName: ${event.toolName}`);
|
|
298
298
|
// 将 params 序列化为 JSON 字符串
|
|
299
299
|
const paramsJson = JSON.stringify(params);
|
|
300
300
|
const paramsHash = calculateContentHash(paramsJson);
|
|
@@ -304,6 +304,6 @@ export async function handleOtherToolInput(event, api, sessionId) {
|
|
|
304
304
|
// 对 source 字段进行长度截断处理
|
|
305
305
|
const adjustedData = adjustContentLength(toolInputData, api, ['content']);
|
|
306
306
|
const postText = JSON.stringify(adjustedData);
|
|
307
|
-
|
|
307
|
+
logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for ${event.toolName}, body length: ${postText.length}`);
|
|
308
308
|
await sendToolInputRequest(postText, api, sessionId);
|
|
309
309
|
}
|