feat: 升级至 v0.3.2 — 工具体系扩展至 26 个 + Context Window 可配置化
## 主要变更 ### 1. Context Window 可配置化(v0.3.1 延续) - DeepSeek/Agnes contextWindow 不再写死 1M,可在设置中配置(min 4096) - 修复 Engine 128K vs Adapter 1M 不一致 bug,Engine 从 adapter.getContextWindow() 读取 - 热重载 configSig 加入 contextWindow,配置变化即时生效 ### 2. 新增 11 个工具(15 → 26 个) - Git 工具集(4):git_status, git_diff, git_log, git_commit - 开发工具集(3):lint_code, run_tests, project_info - HTTP 请求(1):http_request(Node 18+ fetch + AbortController) - TODO 管理(1):todo_write(会话级内存 + LRU 淘汰) - 结构化思考(1):think(无副作用思考空间) - 图片查看(1):view_image(base64 data URL,多模态 LLM 支持) ### 3. 审计修复(2 FAIL + 14 WARN) - FAIL-1: registry.ts truncateResult 添加 dataUrl 白名单(图片不被截断) - FAIL-2: todo.ts 添加 LRU 策略 + clearSession 静态方法 - WARN-1: run_tests filter 字符白名单校验,防 cmd 元字符注入 - WARN-2: dataUrl 检测前置到 stringify 之前,避免大图片无意义序列化 - WARN-3: todo.ts 实现真正 LRU(访问刷新位置,非 FIFO) - WARN-4: git_diff maxBuffer 提升至 5MB,支持超大变更集 - WARN-5: log.info 移至 DelegateTaskTool 注册后,输出正确的 26 - WARN-6: riskColors 添加 critical: 'error' 键 - WARN-7: 所有 execFileAsync 显式设置 encoding: 'utf-8' - WARN-8: git_log --author 拆分为独立参数 - WARN-9: todo_write 权限从 WRITE 改为 READ - WARN-10: description 区分与 task_manager 的不同用途 ### 4. PolicyEngine 策略 - 新增 11 条策略,26 个工具 + mcp_* 全覆盖 - git_commit: WRITE + requireConfirmation - todo_write: READ(内存操作)
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* HTTP 请求工具(1 个)
|
||||
*
|
||||
* http_request — 发送 HTTP/REST API 请求
|
||||
*
|
||||
* 使用 Node.js 18+ 内置 fetch API。
|
||||
* 响应体截断到 50KB 防止结果过大。
|
||||
*/
|
||||
|
||||
import type { IMetonaTool, ToolExecutionContext } from '../../types/metona-tool';
|
||||
import type { MetonaToolDef } from '../../../harness/types';
|
||||
import { MetonaToolCategory, MetonaRiskLevel } from '../../../harness/types';
|
||||
|
||||
const ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'] as const;
|
||||
const MAX_BODY_BYTES = 50 * 1024; // 50KB
|
||||
|
||||
export class HttpRequestTool implements IMetonaTool {
|
||||
readonly definition: MetonaToolDef = {
|
||||
name: 'http_request',
|
||||
description: 'Send an HTTP/REST API request. Supports GET/POST/PUT/PATCH/DELETE/HEAD methods with custom headers and body. Response body is truncated to 50KB.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'Request URL (must start with http:// or https://)' },
|
||||
method: {
|
||||
type: 'string',
|
||||
description: 'HTTP method (default GET)',
|
||||
enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'],
|
||||
},
|
||||
headers: { type: 'object', description: 'Request headers as key-value pairs' },
|
||||
body: { type: 'string', description: 'Request body (string)' },
|
||||
timeout: { type: 'number', description: 'Timeout in milliseconds (default 30000, max 60000)' },
|
||||
},
|
||||
required: ['url'],
|
||||
},
|
||||
category: MetonaToolCategory.NETWORK,
|
||||
riskLevel: MetonaRiskLevel.LOW,
|
||||
requiresPermission: false,
|
||||
timeoutMs: 30_000,
|
||||
};
|
||||
|
||||
async execute(args: Record<string, unknown>, _context: ToolExecutionContext): Promise<unknown> {
|
||||
try {
|
||||
const url = args.url as string;
|
||||
const method = ((args.method as string) ?? 'GET').toUpperCase();
|
||||
const headers = (args.headers as Record<string, string> | undefined) ?? undefined;
|
||||
const body = args.body as string | undefined;
|
||||
const timeout = Math.min(60_000, Math.max(1, (args.timeout as number) ?? 30_000));
|
||||
|
||||
// 校验 URL
|
||||
if (!url || !/^https?:\/\//i.test(url)) {
|
||||
return { error: 'Invalid URL', success: false };
|
||||
}
|
||||
|
||||
// 校验 method
|
||||
if (!(ALLOWED_METHODS as readonly string[]).includes(method)) {
|
||||
return {
|
||||
error: `Invalid method: ${method}. Must be one of: ${ALLOWED_METHODS.join(', ')}`,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
// 超时控制
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
try {
|
||||
const fetchOptions: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
signal: controller.signal,
|
||||
redirect: 'follow',
|
||||
};
|
||||
// GET/HEAD 不应携带 body
|
||||
if (body !== undefined && method !== 'GET' && method !== 'HEAD') {
|
||||
fetchOptions.body = body;
|
||||
}
|
||||
|
||||
const response = await fetch(url, fetchOptions);
|
||||
const text = await response.text();
|
||||
|
||||
// 截断到 50KB
|
||||
const truncated = text.length > MAX_BODY_BYTES;
|
||||
const safeBody = truncated ? text.slice(0, MAX_BODY_BYTES) : text;
|
||||
|
||||
// 只返回 content-type 和 content-length
|
||||
const filteredHeaders: Record<string, string> = {};
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType) filteredHeaders['content-type'] = contentType;
|
||||
const contentLength = response.headers.get('content-length');
|
||||
if (contentLength) filteredHeaders['content-length'] = contentLength;
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: filteredHeaders,
|
||||
body: safeBody,
|
||||
truncated,
|
||||
ok: response.ok,
|
||||
success: true, // v0.3.1 修复 WARN-4: 成功路径添加 success 字段
|
||||
};
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
} catch (error) {
|
||||
// 区分超时(AbortError)与其他网络错误
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
return { error: 'Request timeout', success: false };
|
||||
}
|
||||
const errMsg = error instanceof Error ? error.message : String(error);
|
||||
return { error: errMsg, success: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user