@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.
@@ -2,8 +2,9 @@
2
2
  * 版权所有 (c) 华为技术有限公司 2026-2026
3
3
  */
4
4
  import fs from 'fs';
5
- import path from 'path';
6
- import { CONFIG_FILE_NAME, ENV_FILE_PATH, REQUIRED_ENV_VARS } from './constants.js';
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
- const configPath = path.join(__dirname, CONFIG_FILE_NAME);
44
- if (!fs.existsSync(configPath)) {
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 in ${CONFIG_FILE_NAME}`);
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' in ${CONFIG_FILE_NAME}`);
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' in ${CONFIG_FILE_NAME}`);
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' in ${CONFIG_FILE_NAME}`);
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' in ${CONFIG_FILE_NAME}`);
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' in ${CONFIG_FILE_NAME}`);
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
- api.logger.info(`[SENTINEL HOOK] Config file loaded successfully: ${CONFIG_FILE_NAME}`);
88
+ logger.log(`[SENTINEL HOOK] Config loaded successfully`);
108
89
  return cachedConfig;
109
90
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "api": {
3
+ "timeout": 5000
4
+ },
5
+ "headers": {},
6
+ "skillId": "skill-scope",
7
+ "requestFrom": "openclaw",
8
+ "textSource": "question",
9
+ "action": "TOOL_OUTPUT_SCAN"
10
+ }
@@ -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
- api.logger.info(`[SENTINEL HOOK] before_tool_call_event toolName: ${event.toolName}`);
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
- api.logger.info(`[SENTINEL HOOK] Generated Session ID: ${sessionId}`);
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
- api.logger.error(`[SENTINEL HOOK] Extracted TOOL_INPUT data processing exception: ${error}`);
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
- api.logger.info(`[SENTINEL HOOK] after_tool_call_event toolName: ${event.toolName}`);
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
- api.logger.info(`[SENTINEL HOOK] Generated Session ID: ${sessionId}`);
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
- api.logger.warn(`[SENTINEL HOOK] Text exceeds ${MAX_TOTAL_LENGTH} character limit. Actual length: ${resultTextLength}`);
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
- api.logger.info("[SENTINEL HOOK] No valid information at collection point");
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
- api.logger.warn(`[SENTINEL HOOK] postText exceeds ${MAX_TEXT_LENGTH}.`);
63
+ logger.warn(`[SENTINEL HOOK] postText exceeds ${MAX_TEXT_LENGTH}.`);
63
64
  }
64
65
  }
65
66
  const postText = JSON.stringify(questionText);
66
- api.logger.info(`[SENTINEL HOOK] Content extracted successfully. Length: ${postText.length}`);
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
- api.logger.info(`[SENTINEL HOOK] TOOL_OUTPUT response: status=${result.status}.`);
71
+ logger.log(`[SENTINEL HOOK] TOOL_OUTPUT response: status=${result.status}.`);
71
72
  if (result.status === 'REJECT') {
72
- api.logger.warn('[SENTINEL HOOK] Interrupt handler');
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
- api.logger.error(`[SENTINEL HOOK] Extracted TOOL_OUTPUT data processing exception: ${error}`);
81
+ logger.error(`[SENTINEL HOOK] Extracted TOOL_OUTPUT data processing exception: ${error}`);
81
82
  }
82
83
  });
83
84
  }
@@ -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
- api.logger.warn(`[SENTINEL HOOK] filterText exceeds ${MAX_TEXT_LENGTH}. Original length: ${questionText.length}`);
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
- api.logger.warn(`[SENTINEL HOOK] Field "${fieldName}" truncated by ${overSize} characters to fit ${MAX_TEXT_LENGTH} limit`);
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
- api.logger.warn(`[SENTINEL HOOK] Field "${fieldName}" cleared as it cannot fit within size limit`);
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
- api.logger.info(`[SENTINEL HOOK] TOOL_INPUT response: status=${result.status}`);
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
- api.logger.info('[SENTINEL HOOK] No command found for exec tool');
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
- api.logger.info(`[SENTINEL HOOK] Found ${filePaths.length} file(s) in command`);
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
- api.logger.info(`[SENTINEL HOOK] Sending TOOL_INPUT for file: ${path.basename(filePath)}, body length: ${postText.length}`);
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
- api.logger.error(`[SENTINEL HOOK] Sending TOOL_INPUT Failed: ${e}`);
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
- api.logger.info(`[SENTINEL HOOK] Non-existing files: ${fileNames}`);
259
+ logger.log(`[SENTINEL HOOK] Non-existing files: ${fileNames}`);
260
260
  }
261
261
  }
262
262
  else {
263
263
  // 场景2:直接执行代码(heredoc场景)
264
- api.logger.info('[SENTINEL HOOK] No code files found in command, treating as direct code execution');
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
- api.logger.info(`[SENTINEL HOOK] Sending TOOL_INPUT for direct code execution, body length: ${postText.length}`);
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
- api.logger.info('[SENTINEL HOOK] No message found for message tool');
278
+ logger.log('[SENTINEL HOOK] No message found for message tool');
279
279
  return null;
280
280
  }
281
- api.logger.info(`[SENTINEL HOOK] Processing message tool input, message length: ${message.length}`);
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
- api.logger.info(`[SENTINEL HOOK] Sending TOOL_INPUT for message, body length: ${postText.length}`);
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
- api.logger.info('[SENTINEL HOOK] No params found for tool');
294
+ logger.log('[SENTINEL HOOK] No params found for tool');
295
295
  return;
296
296
  }
297
- api.logger.info(`[SENTINEL HOOK] Processing other tool input, toolName: ${event.toolName}`);
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
- api.logger.info(`[SENTINEL HOOK] Sending TOOL_INPUT for ${event.toolName}, body length: ${postText.length}`);
307
+ logger.log(`[SENTINEL HOOK] Sending TOOL_INPUT for ${event.toolName}, body length: ${postText.length}`);
308
308
  await sendToolInputRequest(postText, api, sessionId);
309
309
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.140-next",
3
+ "version": "0.0.141-next",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",