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,105 @@
|
||||
/**
|
||||
* 图片查看工具(1 个)
|
||||
*
|
||||
* view_image — 读取图片文件并返回 base64 data URL
|
||||
*
|
||||
* 支持格式:png, jpg, jpeg, gif, webp, bmp, svg
|
||||
* 文件大小限制:5MB
|
||||
*/
|
||||
|
||||
import { readFile, stat } from 'fs/promises';
|
||||
import { resolve, extname } from 'path';
|
||||
import type { IMetonaTool, ToolExecutionContext } from '../../types/metona-tool';
|
||||
import type { MetonaToolDef } from '../../../harness/types';
|
||||
import { MetonaToolCategory, MetonaRiskLevel } from '../../../harness/types';
|
||||
import { isPathWithinWorkspace } from './file-guard';
|
||||
|
||||
const MAX_IMAGE_BYTES = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
/** 扩展名 → MIME 映射 */
|
||||
const SUPPORTED_FORMATS: Record<string, string> = {
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
'.bmp': 'image/bmp',
|
||||
'.svg': 'image/svg+xml',
|
||||
};
|
||||
|
||||
const SUPPORTED_EXTENSIONS = Object.keys(SUPPORTED_FORMATS);
|
||||
|
||||
export class ViewImageTool implements IMetonaTool {
|
||||
readonly definition: MetonaToolDef = {
|
||||
name: 'view_image',
|
||||
description:
|
||||
'Read an image file and return its content as a base64 data URL. Supports png, jpg, jpeg, gif, webp, bmp, svg. File size limit: 5MB. Path must be within the workspace.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
path: { type: 'string', description: 'Image file path (relative to workspace or absolute)' },
|
||||
},
|
||||
required: ['path'],
|
||||
},
|
||||
category: MetonaToolCategory.FILESYSTEM,
|
||||
riskLevel: MetonaRiskLevel.SAFE,
|
||||
requiresPermission: false,
|
||||
timeoutMs: 10_000,
|
||||
};
|
||||
|
||||
async execute(args: Record<string, unknown>, context: ToolExecutionContext): Promise<unknown> {
|
||||
try {
|
||||
const path = args.path as string;
|
||||
if (!path) {
|
||||
return { error: 'path is required', success: false };
|
||||
}
|
||||
|
||||
// 安全校验:路径必须在 workspace 内(防范路径遍历)
|
||||
if (!isPathWithinWorkspace(path, context.workspacePath)) {
|
||||
return { error: 'Path outside workspace', path, success: false };
|
||||
}
|
||||
|
||||
// 扩展名校验
|
||||
const ext = extname(path).toLowerCase();
|
||||
const mimeType = SUPPORTED_FORMATS[ext];
|
||||
if (!mimeType) {
|
||||
return {
|
||||
error: 'Unsupported image format',
|
||||
path,
|
||||
supportedFormats: SUPPORTED_EXTENSIONS,
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const resolvedPath = resolve(context.workspacePath, path);
|
||||
|
||||
// 文件存在性 + 大小检查(先 stat 再读取,避免读取超大文件)
|
||||
let stats;
|
||||
try {
|
||||
stats = await stat(resolvedPath);
|
||||
} catch {
|
||||
return { error: 'File not found', path, success: false };
|
||||
}
|
||||
|
||||
if (stats.size > MAX_IMAGE_BYTES) {
|
||||
return { error: 'Image too large (max 5MB)', size: stats.size, success: false };
|
||||
}
|
||||
|
||||
// 读取文件并编码为 base64 data URL
|
||||
const buffer = await readFile(resolvedPath);
|
||||
const dataUrl = `data:${mimeType};base64,${buffer.toString('base64')}`;
|
||||
|
||||
// v0.3.1 修复 WARN-3: 成功路径添加 success: true
|
||||
return {
|
||||
path,
|
||||
size: stats.size,
|
||||
mimeType,
|
||||
dataUrl,
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const errMsg = error instanceof Error ? error.message : String(error);
|
||||
return { error: errMsg, success: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user